From 17fed3f87ce17a7c21c042477629c1f867eef1a2 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sat, 5 Sep 2026 23:38:05 -0500 Subject: [PATCH 01/15] feat(walmart_careers): add Walmart Careers mirror site Mirrors https://careers.walmart.com as WebHarbor site 18 on port 40017. Backend: single Job table with a salaried|hourly discriminator plus Area, Category, Store, User, SavedJob and Application. Routes cover the home page, scored search at /results (token overlap over title/category/area/banner/ city/state/brand, never a strict AND), job detail, save/unsave, a two-step apply flow with a WMC- confirmation number, career-area and resources pages, local email/password auth, account editing, saved roles and applications. Flask-WTF CSRF protects every POST; ?next= is validated as a same-origin relative path. Seed: 200 jobs / 44 stores / 33 categories / 7 areas / 4 benchmark users / 10 saved roles / 4 applications, generated from catalog_source.py by one RNG (random.Random(20260905)) with MIRROR_REFERENCE_DATE instead of any wall-clock read and hard-coded werkzeug hashes. build_seed_database() runs _assert_distractors() at freeze time only; both seed_*() functions are gated as a whole, so /reset restores the DB byte-identically. Frontend: 19 Jinja2 templates on the real Living Design palette, with real brand chrome, fonts and photography harvested from cms.careers.walmart.com and i5.walmartimages.com. Google Maps is replaced by a deterministic server-rendered SVG cluster map on /results and an SVG pin card on the detail page; the LLM search assistant is replaced by ordinary query params. Verified: all 18 sites return 200 on the alt-port container, /reset returns ready:true, and md5(instance) == md5(instance_seed) both before and after a docker restart. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K1NUPpd54bZirE5uzkBgK3 --- .dockerignore | 3 + .gitignore | 1 + Dockerfile | 4 +- control_server.py | 2 +- sites/walmart_careers/.gitignore | 2 + sites/walmart_careers/CLAUDE.md | 52 + sites/walmart_careers/_content.py | 357 +++ sites/walmart_careers/_health.py | 5 + sites/walmart_careers/app.py | 1044 +++++++++ sites/walmart_careers/catalog_source.py | 1932 +++++++++++++++++ sites/walmart_careers/requirements.txt | 5 + sites/walmart_careers/seed_data.py | 554 +++++ sites/walmart_careers/static/css/site.css | 343 +++ .../static/fonts/EverydaySansUI-wght.ttf | Bin 0 -> 166468 bytes .../static/fonts/LivingDesign.woff | Bin 0 -> 10548 bytes sites/walmart_careers/static/icons/.gitkeep | 0 .../static/icons/benefit-financial.svg | 41 + .../static/icons/benefit-growth.svg | 16 + .../static/icons/benefit-health.svg | 23 + .../static/icons/benefit-pto.svg | 18 + .../static/icons/benefit-wellbeing.svg | 30 + sites/walmart_careers/static/icons/expand.svg | 3 + .../static/icons/header-mobile-logo.svg | 18 + .../static/icons/heart-blue.svg | 9 + .../static/icons/home-logo.svg | 18 + .../static/icons/sams-club-text.svg | 4 + .../static/icons/sams-logo.svg | 10 + .../static/icons/sams-spark.svg | 10 + .../static/icons/search_icon.png | Bin 0 -> 2414 bytes .../static/icons/social-facebook.svg | 3 + .../static/icons/social-glassdoor.svg | 3 + .../static/icons/social-instagram.svg | 4 + .../static/icons/social-linkedin.svg | 3 + .../walmart_careers/static/icons/social-x.svg | 3 + .../static/icons/social-youtube.svg | 3 + .../static/icons/spark-white.svg | 8 + .../static/icons/spark-yellow-card.svg | 8 + .../static/icons/spark-yellow.svg | 8 + sites/walmart_careers/static/icons/spark.svg | 8 + .../static/icons/tile-card.svg | 3 + .../static/icons/tile-graduation.svg | 3 + .../static/icons/tile-growth.svg | 12 + .../static/icons/tile-walmart-plus.svg | 3 + .../static/icons/walmart-logo.svg | 8 + sites/walmart_careers/templates/404.html | 14 + .../walmart_careers/templates/_job_card.html | 32 + sites/walmart_careers/templates/account.html | 22 + .../templates/account_edit.html | 41 + .../templates/applications.html | 32 + .../templates/apply_confirm.html | 23 + .../templates/apply_contact.html | 41 + .../templates/apply_submitted.html | 22 + sites/walmart_careers/templates/area.html | 82 + sites/walmart_careers/templates/base.html | 119 + .../templates/hiring_process.html | 42 + sites/walmart_careers/templates/index.html | 120 + .../walmart_careers/templates/job_detail.html | 175 ++ .../walmart_careers/templates/locations.html | 33 + sites/walmart_careers/templates/login.html | 26 + sites/walmart_careers/templates/register.html | 38 + sites/walmart_careers/templates/results.html | 172 ++ .../templates/saved_roles.html | 36 + sites/walmart_careers/templates/terms.html | 14 + websyn_start.sh | 13 +- 64 files changed, 5672 insertions(+), 9 deletions(-) create mode 100644 sites/walmart_careers/.gitignore create mode 100644 sites/walmart_careers/CLAUDE.md create mode 100644 sites/walmart_careers/_content.py create mode 100644 sites/walmart_careers/_health.py create mode 100644 sites/walmart_careers/app.py create mode 100644 sites/walmart_careers/catalog_source.py create mode 100644 sites/walmart_careers/requirements.txt create mode 100644 sites/walmart_careers/seed_data.py create mode 100644 sites/walmart_careers/static/css/site.css create mode 100644 sites/walmart_careers/static/fonts/EverydaySansUI-wght.ttf create mode 100644 sites/walmart_careers/static/fonts/LivingDesign.woff create mode 100644 sites/walmart_careers/static/icons/.gitkeep create mode 100644 sites/walmart_careers/static/icons/benefit-financial.svg create mode 100644 sites/walmart_careers/static/icons/benefit-growth.svg create mode 100644 sites/walmart_careers/static/icons/benefit-health.svg create mode 100644 sites/walmart_careers/static/icons/benefit-pto.svg create mode 100644 sites/walmart_careers/static/icons/benefit-wellbeing.svg create mode 100644 sites/walmart_careers/static/icons/expand.svg create mode 100644 sites/walmart_careers/static/icons/header-mobile-logo.svg create mode 100644 sites/walmart_careers/static/icons/heart-blue.svg create mode 100644 sites/walmart_careers/static/icons/home-logo.svg create mode 100644 sites/walmart_careers/static/icons/sams-club-text.svg create mode 100644 sites/walmart_careers/static/icons/sams-logo.svg create mode 100644 sites/walmart_careers/static/icons/sams-spark.svg create mode 100644 sites/walmart_careers/static/icons/search_icon.png create mode 100644 sites/walmart_careers/static/icons/social-facebook.svg create mode 100644 sites/walmart_careers/static/icons/social-glassdoor.svg create mode 100644 sites/walmart_careers/static/icons/social-instagram.svg create mode 100644 sites/walmart_careers/static/icons/social-linkedin.svg create mode 100644 sites/walmart_careers/static/icons/social-x.svg create mode 100644 sites/walmart_careers/static/icons/social-youtube.svg create mode 100644 sites/walmart_careers/static/icons/spark-white.svg create mode 100644 sites/walmart_careers/static/icons/spark-yellow-card.svg create mode 100644 sites/walmart_careers/static/icons/spark-yellow.svg create mode 100644 sites/walmart_careers/static/icons/spark.svg create mode 100644 sites/walmart_careers/static/icons/tile-card.svg create mode 100644 sites/walmart_careers/static/icons/tile-graduation.svg create mode 100644 sites/walmart_careers/static/icons/tile-growth.svg create mode 100644 sites/walmart_careers/static/icons/tile-walmart-plus.svg create mode 100644 sites/walmart_careers/static/icons/walmart-logo.svg create mode 100644 sites/walmart_careers/templates/404.html create mode 100644 sites/walmart_careers/templates/_job_card.html create mode 100644 sites/walmart_careers/templates/account.html create mode 100644 sites/walmart_careers/templates/account_edit.html create mode 100644 sites/walmart_careers/templates/applications.html create mode 100644 sites/walmart_careers/templates/apply_confirm.html create mode 100644 sites/walmart_careers/templates/apply_contact.html create mode 100644 sites/walmart_careers/templates/apply_submitted.html create mode 100644 sites/walmart_careers/templates/area.html create mode 100644 sites/walmart_careers/templates/base.html create mode 100644 sites/walmart_careers/templates/hiring_process.html create mode 100644 sites/walmart_careers/templates/index.html create mode 100644 sites/walmart_careers/templates/job_detail.html create mode 100644 sites/walmart_careers/templates/locations.html create mode 100644 sites/walmart_careers/templates/login.html create mode 100644 sites/walmart_careers/templates/register.html create mode 100644 sites/walmart_careers/templates/results.html create mode 100644 sites/walmart_careers/templates/saved_roles.html create mode 100644 sites/walmart_careers/templates/terms.html diff --git a/.dockerignore b/.dockerignore index a921bf15..1c7a6ffe 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,6 +8,9 @@ sites/*/instance/ # Don't ship — runtime doesn't need scrape intermediate (data is in instance_seed/*.db). sites/*/scraped_data/ +# Don't ship — per-site local dev helpers (asset harvest, smoke tests, leak audits). +sites/*/scripts_dev/ + # Don't ship — bytecode / venvs. sites/*/__pycache__/ **/__pycache__/ diff --git a/.gitignore b/.gitignore index 24ce1529..c4da48d3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ sites/*/static/external_cache/ # ============================================================= # scrape pipeline intermediate; runtime data lives in instance_seed/*.db sites/*/scraped_data/ +/scraped_data/ # rebuilt at every container boot from instance_seed/ sites/*/instance/ sites/*/venv/ diff --git a/Dockerfile b/Dockerfile index c1b198e4..da8d6a42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # WebHarbor — slim, self-contained image. -# 17 Flask mirror sites + control plane on :8101. +# 18 Flask mirror sites + control plane on :8101. FROM python:3.12-slim-bookworm @@ -36,6 +36,6 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -EXPOSE 8101 40000-40016 +EXPOSE 8101 40000-40017 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index facf07f1..68fee463 100644 --- a/control_server.py +++ b/control_server.py @@ -26,7 +26,7 @@ 'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking', 'github', 'google_flights', 'google_map', 'google_search', 'huggingface', 'wolfram_alpha', 'cambridge_dictionary', - 'coursera', 'espn', 'merriam_webster', 'ikea', + 'coursera', 'espn', 'merriam_webster', 'ikea', 'walmart_careers', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/walmart_careers/.gitignore b/sites/walmart_careers/.gitignore new file mode 100644 index 00000000..a34abb5d --- /dev/null +++ b/sites/walmart_careers/.gitignore @@ -0,0 +1,2 @@ +# Local build/inspection helpers: never shipped with the site. +scripts_dev/ diff --git a/sites/walmart_careers/CLAUDE.md b/sites/walmart_careers/CLAUDE.md new file mode 100644 index 00000000..852df243 --- /dev/null +++ b/sites/walmart_careers/CLAUDE.md @@ -0,0 +1,52 @@ +# walmart_careers — site notes + +Mirror of https://careers.walmart.com. Port **40017** (index 17 in `websyn_start.sh`); +alt-port test container maps it to **41017**. + +## Layout + +| file | role | +|---|---| +| `app.py` | models, routes, scored search, deterministic SVG maps, bootstrap | +| `catalog_source.py` | the source catalog: areas, categories, 44 stores, 37 hourly + 29 salaried title families with explicit placements | +| `seed_data.py` | turns the catalog into SQLite; `build_seed_database()` is the freezer | +| `_content.py` | CMS-style prose, design constants, US/PR map outlines | +| `templates/` | 18 Jinja2 templates + `_job_card.html` macro | +| `scripts_dev/` | local-only helpers (asset harvest, smoke test); gitignored and dockerignored | + +## Rebuilding the seed DB + +```bash +cd sites/walmart_careers +PYTHONHASHSEED=0 python seed_data.py # writes instance_seed/walmart_careers.db +``` + +Run it twice and compare md5s — the build is byte-reproducible. `build_seed_database()` +also runs `_assert_distractors()`, which fails the build if a catalog edit breaks a +volume invariant (jobs per category/store/state/shift) or a task's near-miss set. +`_assert_distractors()` never runs at import or at `/reset` time. + +## Determinism rules that must hold + +- one RNG: `random.Random(20260905)` in `seed_data.py`, nothing else +- `MIRROR_REFERENCE_DATE` instead of `date.today()`; no `utcnow()`/`now()` anywhere on the + import or bootstrap path (runtime writes may use `now()` — `/reset` wipes them) +- the four werkzeug password hashes are hard-coded (werkzeug salts randomly) +- `seed_database()` and `seed_benchmark_users()` are each gated as a whole + +## Assets + +Brand chrome (`static/icons/`, `static/fonts/`) is committed; photography +(`static/images/`) is HF-managed. Everything was pulled from the live site: +`cms.careers.walmart.com/content/dam/careers/...`, `careers.walmart.com/assets/svgs/...`, +and the `EverydaySansUI` / `LivingDesign` font files from `i5.walmartimages.com`. +`scripts_dev/harvest_assets.py` records the exact URL → filename mapping and re-downloads +them; images are then downscaled to 1600px wide (16 MB total). + +## Things that are deliberately not mirrored + +Google Maps (replaced by a deterministic server-rendered SVG cluster map on `/results` +and an SVG pin card on the detail page), the LLM search assistant (replaced by ordinary +query params with token-overlap scoring), Workday/OIDC login (local email + password), +the OTP apply flow (two-step local form), and the Future roles / Content tabs (explicit +empty-state panels). diff --git a/sites/walmart_careers/_content.py b/sites/walmart_careers/_content.py new file mode 100644 index 00000000..171a07ad --- /dev/null +++ b/sites/walmart_careers/_content.py @@ -0,0 +1,357 @@ +"""Static CMS-style copy and design constants for the Walmart Careers mirror. + +Nothing in here is runtime data: these are the marketing strings, benefit tiles +and map outlines that the real site serves from AEM. Runtime data (jobs, stores, +users, saved roles, applications) lives in SQLite only. +""" +from __future__ import annotations + +from datetime import date + +# The mirror is frozen against this date. Never call date.today() anywhere in +# the seed or bootstrap path. +MIRROR_REFERENCE_DATE = date(2026, 8, 31) + +SITE_NAME = "Walmart Careers" +COPYRIGHT = "©2026 Walmart Inc." + +# Job ids surfaced as "Trending roles" on the home page and the hiring page. +TRENDING_JOB_IDS = [ + "R-2463275", + "R-2451180", + "CP-9046-11101", +] + +HERO_HEADLINE_1 = "Cashiers wanted." +HERO_HEADLINE_2 = "Next move, yours." +SEARCH_PLACEHOLDER = "Search by team, department, keyword" + +CAROUSEL = [ + ("Jorden", "Associate Merchant", "Corporate Careers", "corporate", "home-tile-corporate.png"), + ("Tatiana", "Software Engineer", "Tech Careers", "technology", "home-tile-tech.png"), + ("Jamaily", "Club Manager", "Stores & Clubs Careers", "stores-and-clubs", "home-tile-stores.png"), + ("Caleb", "Maintenance Tech", "Supply Chain Careers", "supply-chain-and-transportation", "home-tile-supply.png"), + ("Yasinya", "Pharmacy Tech", "Healthcare Careers", "healthcare", "home-tile-health.png"), +] + +VALUES = [ + ("Respect for the individual", "We listen, we support, and we help each other grow."), + ("Service to the customer", "Everything starts with the people who shop with us."), + ("Strive for excellence", "We look for a better way, every single day."), + ("Act with integrity", "We do the right thing, especially when it is hard."), +] + +BENEFIT_ROWS = [ + ("Financial perks", "Enjoy 401(k) matching and stock purchase plans.", "benefit-financial.svg"), + ("Paid time off", "Take a break as needed for vacations, sick leave, holidays, parental leave and more.", "benefit-pto.svg"), + ("Comprehensive health benefits", "Medical, dental, vision and wellness programs for you and your family.", "benefit-health.svg"), + ("Wellbeing programs", "Access mental health resources and assistance programs for life's challenges.", "benefit-wellbeing.svg"), + ("Career growth opportunities", "Training, leadership programs, and clear paths to advance.", "benefit-growth.svg"), +] + +BENEFIT_FOOTNOTE = ( + "That's just the beginning. We offer more perks specific to your work location and role." +) + +STAT_CARDS = [ + ("$1 billion", "invested in associate career training and development"), + ("75%", "of salaried managers began as hourly associates"), + ("300,000", "associates have earned a 10+ year badge"), + ("120,000", "U.S. associates have participated in Live Better U"), +] + +DAY_IN_THE_LIFE = [ + ("Store Coach", "Day in the life", "life-associates.jpg"), + ("Optician", "Day in the life", "life-8th-plate.jpg"), + ("Store Manager", "Day in the life", "life-crystal-bridges.jpg"), + ("Pharmacy Tech", "Day in the life", "life-amp.jpg"), +] + +# --------------------------------------------------------------------------- # +# Benefit tiles on the job detail page. Keyed by brand; hourly and salaried +# postings surface a slightly different Live Better U line, exactly as upstream. +# --------------------------------------------------------------------------- # +_WALMART_PLUS_TILE = ( + "Walmart+", + "Free shipping", + "As a Walmart Associate, you're eligible to become a Walmart+ member. Enjoy benefits " + "like free store delivery and shipping, fuel savings, and video streaming. Sam's Club " + "associates are eligible for a Club Membership.", + "tile-walmart-plus.svg", +) +_DISCOUNT_TILE = ( + "Discount Card", + "Get 10% off", + "Walmart associates are eligible for a 10% discount card on all general merchandise " + "items and fresh produce in-store and on select items at Walmart.com. Eligible after " + "90 days of employment.", + "tile-card.svg", +) +_LBU_FIELD_TILE = ( + "Live Better U", + "100% covered", + "Earn a degree or in-demand skills certificates with no debt - Walmart covers 100% of " + "tuition and books. Live Better U offers 60+ programs for Associates to pursue their dreams.", + "tile-graduation.svg", +) +_LBU_CORP_TILE = ( + "Live Better U", + "100% covered", + "Through Live Better U, Walmart and Sam's Club associates can learn critical skills and " + "create pathways for promotion into in-demand jobs within the company. Whether earning a " + "college degree, certificate or high school diploma, Walmart pays for tuition and books.", + "tile-graduation.svg", +) +_ACADEMY_TILE = ( + "Walmart Academy", + "Grow your skills", + "Ready to grow your career? Walmart Academy offers job-specific retail training and " + "leadership courses to help Associates reach their career goals.", + "tile-growth.svg", +) + + +def benefit_tiles_for(brand: str, population: str) -> list[tuple[str, str, str, str]]: + first = _DISCOUNT_TILE if population == "salaried" else _WALMART_PLUS_TILE + second = _LBU_CORP_TILE if population == "salaried" else _LBU_FIELD_TILE + return [first, second, _ACADEMY_TILE] + + +JOB_BENEFIT_ROWS = [ + ("Financial perks", "Enjoy 401(k) matching and stock purchase plans", "benefit-financial.svg"), + ("Wellbeing programs", "Access mental health resources and assistance programs for life's challenges", "benefit-wellbeing.svg"), + ("Paid time off", "Take a break as needed for vacation, sick leave, holidays, parental leave, and more", "benefit-pto.svg"), + ("Career growth opportunities", "Training, leadership programs, and clear paths to advance", "benefit-growth.svg"), + ("Comprehensive health benefits", "Medical, dental, vision, and wellness programs for you and your family", "benefit-health.svg"), +] + +LIFE_AT_WALMART_HEADING = "Life at Walmart" +LIFE_AT_WALMART = [ + "At Walmart, you're welcome for who you are, no matter your background, experiences, or perspectives.", + "Our stores and services are for everyone, and so is our workplace. We believe different experiences " + "drive our ability to better serve our communities and deliver affordable products across the nation.", + "Here, your unique insights and ideas are encouraged, valued, and essential to creating a " + "forward-thinking company that thrives on fresh ideas and dedicated teamwork.", + "Since our founding, we've focused on bringing affordable essentials to families everywhere, and " + "today, Walmart is one of the most recognizable names in retail worldwide.", +] +LIFE_AT_WALMART_QUOTE = ( + "Join us, and help us continue our mission to bring everyday value and support to communities everywhere." +) + +DRUG_FREE_NOTICE = ( + "Walmart is committed to maintaining a drug-free workplace and has a no tolerance policy regarding " + "the use of illegal drugs and alcohol on the job. This policy applies to all employees and aims to " + "create a safe and productive work environment." +) + +HOURLY_PAY_NOTICE = [ + "The actual hourly rate will equal or exceed the required minimum wage applicable to the job location.", + "Additional compensation includes annual or quarterly performance incentives.", + "Additional compensation in the form of premiums may be paid in amounts ranging from $0.35 per hour " + "to $3.00 per hour in specific circumstances. Premiums may be based on schedule, facility, season, " + "or specific work performed. Multiple premiums may apply if applicable criteria are met.", +] + +MIN_QUAL_PREAMBLE = ( + "Outlined below are the required minimum qualifications for this position. If none are listed, " + "there are no minimum qualifications." +) +PREF_QUAL_PREAMBLE = ( + "Outlined below are the optional preferred qualifications for this position. If none are listed, " + "there are no preferred qualifications." +) + +# --------------------------------------------------------------------------- # +# Resources pages +# --------------------------------------------------------------------------- # +LOCATIONS_HEADING = "Our locations" +LOCATIONS_BLURB = ( + "Our hubs spark collaboration and innovation, so you're free to energize and push boundaries " + "from the space that serves you best." +) +HUB_COPY = { + "10101": ( + "Northwest Arkansas", + "Northwest Arkansas offers trails, local eats, and the Crystal Bridges Museum - while our " + "12 new Home Office buildings reflect the company's story through thoughtful design.", + "loc-nwa.jpg", + ), + "11807": ( + "Sunnyvale", + "A weekend hike through the mountains. An evening walk next to the ocean. A quick visit to a " + "museum. The best of both worlds - work and leisure - are waiting for you right here.", + "loc-sunnyvale.jpg", + ), + "11003": ( + "Hoboken", + "Just across from Lower Manhattan, Hoboken is a walkable, character-filled town on the Hudson " + "with a truly unique charm.", + "loc-hoboken.jpg", + ), + "11500": ( + "Dallas", + "Our Dallas office anchors merchandising, finance and supply chain teams in the middle of one " + "of the fastest-growing metros in the country.", + "loc-dc-metro.jpg", + ), +} +LOCATIONS_CLOSING = ( + "Between making an impact at scale and our culture of promoting from within, from coders all the " + "way to cashiers, Walmart is the best place to build a career, period." +) + +HIRING_HEADING = "How we hire" +HIRING_BLURB = ( + "Every career starts with a first step. Whether you're applying for your first job or your next " + "big move, this is the beginning of something new. At Walmart and Sam's Club, the hiring process " + "is about more than landing a role, it's about discovering where you belong, where you can grow, " + "and where your work can make a real difference." +) +HIRING_STEPS = [ + ("1. Find your role", "Search open roles by keyword, career area or location, then save the ones you like."), + ("2. Apply online", "Share your contact details and work history. Most applications take 20-25 minutes."), + ("3. Interview", "A recruiter or hiring manager reaches out, usually within a week of your application."), + ("4. Offer and onboarding", "Accept your offer, complete pre-employment steps and pick your start date."), +] +HIRING_FAQ = [ + ( + "Before you apply", + [ + ( + "Do I need a resume or CV to apply for all Walmart jobs?", + "Not necessarily. A resume or CV is not required to apply, but you will need to provide " + "details about your job history and other information on the application. If you would " + "like to include your resume, LinkedIn profile, portfolio or website, there will be a " + "section where you can add it to your application.", + ), + ( + "How long does it take to fill out an application on average?", + "On average, it takes 20-25 minutes to complete your application for the first time. " + "Subsequent applications will take less time to apply as our system saves your " + "application information.", + ), + ( + "Can I start the application process and finish it later?", + "For hourly roles within the Walmart Online Hiring Center, you have the ability to save " + "your work and log back in at a later time.", + ), + ( + "Can I change my application after submitting?", + "No, you cannot change your application after submitting. Please make sure that " + "everything is finalized before you hit the submit button.", + ), + ], + ), + ( + "After you apply", + [ + ( + "Will I receive confirmation that my application was successfully submitted?", + "Yes. Once you complete your application you will see a confirmation screen with a " + "confirmation number that starts with WMC-.", + ), + ( + "When should I expect to hear back after submitting my application?", + "Timing varies, but we try to respond to applicants within a week of submission.", + ), + ( + "Will I be notified if I am not selected for an interview?", + "Yes, you will be informed if you are not selected for an interview at this time.", + ), + ( + "Do you provide reasonable accommodations during the application process?", + "Yes, reach out to your manager, recruiter or recruiting coordinator about any needs " + "you have. We are happy to do what we can to support you.", + ), + ], + ), +] + +TERMS_HEADING = "Terms & Conditions" +TERMS_SECTIONS = [ + ( + "About this mirror", + "This is an offline WebHarbor mirror of careers.walmart.com built for agent benchmarking. " + "No application submitted here reaches Walmart Inc., and no data leaves the container.", + ), + ( + "Candidate accounts", + "Accounts created on this mirror exist only inside the local database and are removed whenever " + "the environment is reset to its seed state.", + ), + ( + "Applications", + "Submitting an application records a row in the local database and returns a confirmation number " + "in the form WMC-000000. It creates no relationship, express or implied, with Walmart Inc.", + ), + ( + "Accuracy of postings", + "Job postings, pay ranges, store addresses and requisition IDs shown here are synthetic mirror " + "data modelled on the structure of the real site.", + ), +] + +# --------------------------------------------------------------------------- # +# Footer +# --------------------------------------------------------------------------- # +FOOTER_CAREER_LINKS = [ + ("Stores and Clubs", "stores-and-clubs"), + ("Supply Chain and Transportation", "supply-chain-and-transportation"), + ("Healthcare", "healthcare"), + ("Technology", "technology"), + ("Corporate", "corporate"), +] +FOOTER_BRANDS = ["Walmart", "Sam's Club", "VIZIO"] +FOOTER_SOCIAL = [ + ("Facebook", "social-facebook.svg"), + ("Instagram", "social-instagram.svg"), + ("LinkedIn", "social-linkedin.svg"), + ("X", "social-x.svg"), + ("YouTube", "social-youtube.svg"), + ("Glassdoor", "social-glassdoor.svg"), +] +FOOTER_EEO = ( + "Walmart, Inc. is an Equal Opportunity Employer. We believe we are best equipped to help our " + "associates, customers, and the communities we serve live better when we really know them. That " + "means understanding, respecting, and valuing unique styles, experiences, identities, abilities, " + "ideas and opinions- while welcoming all people. Walmart Inc. participates in E-verify. Learn more " + "about applicant rights under Federal Employment Laws." +) +FOOTER_BENEFITS_NOTE = ( + "Eligibility for benefits depends on your job classification, and benefits are subject to specific " + "plan or program terms. For more information about your benefits options, please see the Associate " + "Benefits Book at One.Walmart.com/BenefitsBook." +) + +EMPTY_FUTURE_ROLES = ( + "Future roles are not part of this mirror. Every posting in this environment is an open role you " + "can browse, save and apply to from the Open roles tab." +) +EMPTY_CONTENT_TAB = ( + "Content search is not part of this mirror. Use the Open roles tab, the career area pages or the " + "Resources pages to explore this site." +) + +# --------------------------------------------------------------------------- # +# Coarse lat/lng outlines used by the deterministic server-rendered cluster map. +# Points are (lng, lat). +# --------------------------------------------------------------------------- # +US_OUTLINE = [ + (-124.7, 48.4), (-123.0, 48.2), (-122.6, 47.0), (-124.0, 46.3), (-124.1, 43.7), + (-124.4, 42.0), (-124.2, 40.4), (-122.4, 37.8), (-121.9, 36.6), (-120.6, 34.6), + (-118.4, 33.7), (-117.1, 32.5), (-114.7, 32.7), (-111.1, 31.3), (-108.2, 31.3), + (-106.5, 31.8), (-104.9, 30.6), (-103.1, 29.0), (-101.4, 29.8), (-99.1, 26.4), + (-97.1, 25.9), (-97.4, 28.0), (-95.0, 29.1), (-93.8, 29.7), (-91.0, 29.2), + (-89.4, 29.0), (-89.0, 30.2), (-87.5, 30.3), (-85.0, 29.7), (-84.0, 30.1), + (-82.8, 27.9), (-81.8, 25.9), (-80.1, 25.2), (-80.1, 27.0), (-81.4, 30.7), + (-80.8, 32.0), (-78.9, 33.7), (-75.7, 35.2), (-76.0, 36.9), (-75.1, 38.3), + (-74.0, 39.7), (-73.9, 40.6), (-71.9, 41.3), (-70.0, 41.7), (-70.2, 42.6), + (-70.8, 43.2), (-69.0, 43.9), (-67.0, 44.8), (-67.8, 45.7), (-69.2, 47.5), + (-71.5, 45.0), (-74.7, 45.0), (-76.9, 43.3), (-79.2, 43.4), (-82.4, 41.7), + (-83.1, 42.2), (-82.5, 45.3), (-84.4, 46.5), (-87.6, 46.0), (-88.0, 48.2), + (-89.5, 48.0), (-95.2, 49.0), (-104.0, 49.0), (-116.0, 49.0), (-123.0, 49.0), +] +PR_OUTLINE = [ + (-67.3, 18.5), (-66.4, 18.5), (-65.6, 18.4), (-65.6, 17.9), (-66.6, 17.9), (-67.3, 18.1), +] diff --git a/sites/walmart_careers/_health.py b/sites/walmart_careers/_health.py new file mode 100644 index 00000000..cd932bcc --- /dev/null +++ b/sites/walmart_careers/_health.py @@ -0,0 +1,5 @@ +"""Per-site health probe (optional, called by control_server).""" + + +def health(): + return {"ok": True, "site": "walmart_careers"} diff --git a/sites/walmart_careers/app.py b/sites/walmart_careers/app.py new file mode 100644 index 00000000..904ac891 --- /dev/null +++ b/sites/walmart_careers/app.py @@ -0,0 +1,1044 @@ +"""Walmart Careers local mirror for WebHarbor.""" +from __future__ import annotations + +import json +import math +import os +import re +import sys +from datetime import date, datetime +from pathlib import Path +from urllib.parse import urlencode + +from flask import ( + Flask, + abort, + flash, + jsonify, + redirect, + render_template, + request, + session, + url_for, +) +from flask_login import ( + LoginManager, + UserMixin, + current_user, + login_required, + login_user, + logout_user, +) +from flask_sqlalchemy import SQLAlchemy +from flask_wtf.csrf import CSRFProtect +from werkzeug.security import check_password_hash, generate_password_hash + +import _content as content + +BASE_DIR = Path(__file__).resolve().parent +INSTANCE_DIR = BASE_DIR / "instance" +DB_PATH = INSTANCE_DIR / "walmart_careers.db" + +INSTANCE_DIR.mkdir(parents=True, exist_ok=True) + +app = Flask(__name__, instance_path=str(INSTANCE_DIR)) +app.config["SECRET_KEY"] = "webharbor-walmart-careers-demo-key" +app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_PATH}" +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False +app.config["WTF_CSRF_TIME_LIMIT"] = None + +db = SQLAlchemy(app) +csrf = CSRFProtect(app) +login_manager = LoginManager(app) +login_manager.login_view = "login" +login_manager.login_message = "Sign in to continue." +login_manager.login_message_category = "info" + +DEMO_PASSWORD = "TestPass123!" +EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$") +PAGE_SIZE = 10 + +SHIFT_VALUES = [ + "Weekday Day", + "Weekday Evening", + "Weekday Overnight", + "Weekend Day", + "Weekend Evening", + "Weekend Overnight", + "Flex", +] +BRAND_VALUES = ["Vizio", "Walmart", "Sam's Club"] +EMPLOYMENT_TYPE_VALUES = ["Full time", "Part time", "Intern"] +RATE_VALUES = ["Salaried", "Hourly"] +RADIUS_VALUES = [5, 15, 25, 60] +SORT_VALUES = ["relevance", "most_recent"] + +STOPWORDS = { + "a", "an", "and", "at", "for", "in", "of", "on", "or", "the", "to", "with", + "jobs", "job", "roles", "role", "near", "me", "all", +} + + +# --------------------------------------------------------------------------- # +# Models +# --------------------------------------------------------------------------- # +class Area(db.Model): + __tablename__ = "areas" + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(64), unique=True, nullable=False) + name = db.Column(db.String(80), nullable=False) + display_order = db.Column(db.Integer, nullable=False, default=0) + blurb = db.Column(db.Text, nullable=False, default="") + hero_image = db.Column(db.String(120), nullable=False, default="") + has_index_page = db.Column(db.Boolean, nullable=False, default=True) + is_filterable = db.Column(db.Boolean, nullable=False, default=True) + + categories = db.relationship( + "Category", backref="area", lazy="select", + order_by="Category.display_order", + ) + + @property + def url(self) -> str: + return url_for("career_area", slug=self.slug) + + +class Category(db.Model): + __tablename__ = "categories" + id = db.Column(db.Integer, primary_key=True) + area_id = db.Column(db.Integer, db.ForeignKey("areas.id"), nullable=False) + name = db.Column(db.String(120), nullable=False) + slug = db.Column(db.String(120), nullable=False) + display_order = db.Column(db.Integer, nullable=False, default=0) + + __table_args__ = (db.UniqueConstraint("area_id", "slug", name="uq_category_area_slug"),) + + +class Store(db.Model): + __tablename__ = "stores" + id = db.Column(db.Integer, primary_key=True) + store_number = db.Column(db.String(16), unique=True, nullable=False) + banner = db.Column(db.String(64), nullable=False) + location_name = db.Column(db.String(120), nullable=False) + street = db.Column(db.String(160), nullable=False) + city = db.Column(db.String(80), nullable=False) + state = db.Column(db.String(2), nullable=False) + zip = db.Column(db.String(12), nullable=False) + lat = db.Column(db.Float, nullable=False) + lng = db.Column(db.Float, nullable=False) + is_hub = db.Column(db.Boolean, nullable=False, default=False) + is_office = db.Column(db.Boolean, nullable=False, default=False) + + @property + def banner_line(self) -> str: + return f"{self.banner} #{self.store_number}" + + @property + def city_state(self) -> str: + return f"{self.city}, {self.state}" + + +class Job(db.Model): + __tablename__ = "jobs" + job_id = db.Column(db.String(32), primary_key=True) + population = db.Column(db.String(16), nullable=False) # salaried | hourly + title = db.Column(db.String(160), nullable=False) + brand = db.Column(db.String(24), nullable=False) + store_id = db.Column(db.Integer, db.ForeignKey("stores.id"), nullable=False) + area_id = db.Column(db.Integer, db.ForeignKey("areas.id"), nullable=False) + category_id = db.Column(db.Integer, db.ForeignKey("categories.id"), nullable=False) + shifts_json = db.Column(db.Text, nullable=False, default="[]") + employment_type = db.Column(db.String(16), nullable=False) + pay_frequency = db.Column(db.String(8), nullable=False) # Hourly | Annual + min_pay = db.Column(db.Numeric(10, 2), nullable=False) + max_pay = db.Column(db.Numeric(10, 2), nullable=False) + posted_date = db.Column(db.Date, nullable=False) + sort_rank = db.Column(db.Integer, nullable=False, default=0) + summary = db.Column(db.Text, nullable=False, default="") + description = db.Column(db.Text, nullable=False, default="") + additional_description_json = db.Column(db.Text, nullable=True) + hashtag = db.Column(db.String(48), nullable=True) + shift_time = db.Column(db.String(120), nullable=True) + positions_available = db.Column(db.Integer, nullable=True) + min_age_note = db.Column(db.Boolean, nullable=False, default=False) + worker_type = db.Column(db.String(48), nullable=True) + job_posting_id = db.Column(db.String(48), nullable=True) + min_qualifications_json = db.Column(db.Text, nullable=True) + preferred_qualifications = db.Column(db.Text, nullable=True) + hero_images_json = db.Column(db.Text, nullable=False, default="[]") + + store = db.relationship("Store", lazy="joined") + area = db.relationship("Area", lazy="joined") + category = db.relationship("Category", lazy="joined") + + # -- derived display helpers ------------------------------------------- # + @property + def shifts(self) -> list[str]: + return json.loads(self.shifts_json or "[]") + + @property + def shift_label(self) -> str: + values = self.shifts + if len(values) == 1: + return values[0] + return "Multiple shifts" + + @property + def is_salaried(self) -> bool: + return self.population == "salaried" + + @property + def rate_label(self) -> str: + return "Salaried" if self.is_salaried else "Hourly" + + @property + def pay_suffix(self) -> str: + return "/yr" if self.pay_frequency == "Annual" else "/hr" + + @property + def pay_range(self) -> str: + if self.pay_frequency == "Annual": + return f"${int(self.min_pay):,} - ${int(self.max_pay):,}/yr" + return f"${float(self.min_pay):,.2f} - ${float(self.max_pay):,.2f}/hr" + + @property + def additional_description(self) -> list[str]: + return json.loads(self.additional_description_json or "[]") + + @property + def min_qualifications(self) -> list[str]: + return json.loads(self.min_qualifications_json or "[]") + + @property + def hero_images(self) -> list[str]: + return json.loads(self.hero_images_json or "[]") + + @property + def url(self) -> str: + return url_for("job_detail", job_id=self.job_id) + + @property + def search_blob(self) -> str: + """Fields the scored search reads. Description is deliberately excluded.""" + return " ".join( + [ + self.title, + self.category.name if self.category else "", + self.area.name if self.area else "", + self.store.banner if self.store else "", + self.store.city if self.store else "", + self.store.state if self.store else "", + self.brand, + ] + ) + + +class User(UserMixin, db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(160), unique=True, nullable=False) + username = db.Column(db.String(80), unique=True, nullable=False) + display_name = db.Column(db.String(120), nullable=False, default="") + first_name = db.Column(db.String(80), nullable=False, default="") + last_name = db.Column(db.String(80), nullable=False, default="") + phone = db.Column(db.String(32), nullable=False, default="") + city = db.Column(db.String(80), nullable=False, default="") + state = db.Column(db.String(2), nullable=False, default="") + password_hash = db.Column(db.String(256), nullable=False) + created_at = db.Column(db.DateTime, nullable=False) + + def check_password(self, raw: str) -> bool: + return check_password_hash(self.password_hash, raw) + + +class SavedJob(db.Model): + __tablename__ = "saved_jobs" + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) + job_id = db.Column(db.String(32), db.ForeignKey("jobs.job_id"), nullable=False) + saved_at = db.Column(db.DateTime, nullable=False) + + __table_args__ = (db.UniqueConstraint("user_id", "job_id", name="uq_saved_user_job"),) + + job = db.relationship("Job", lazy="joined") + + +class Application(db.Model): + __tablename__ = "applications" + id = db.Column(db.Integer, primary_key=True) + job_id = db.Column(db.String(32), db.ForeignKey("jobs.job_id"), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True) + email = db.Column(db.String(160), nullable=False) + first_name = db.Column(db.String(80), nullable=False) + last_name = db.Column(db.String(80), nullable=False) + phone = db.Column(db.String(32), nullable=False) + status = db.Column(db.String(32), nullable=False, default="Submitted") + confirmation_no = db.Column(db.String(32), unique=True, nullable=False) + submitted_at = db.Column(db.DateTime, nullable=False) + + job = db.relationship("Job", lazy="joined") + + +@login_manager.user_loader +def load_user(user_id: str): + return db.session.get(User, int(user_id)) + + +# --------------------------------------------------------------------------- # +# Helpers +# --------------------------------------------------------------------------- # +def dumps_json(value) -> str: + return json.dumps(value, ensure_ascii=False, sort_keys=False, separators=(",", ":")) + + +def confirmation_for(application_id: int) -> str: + return f"WMC-{application_id:06d}" + + +def tokenize(text: str) -> list[str]: + return [t for t in re.split(r"[^a-z0-9']+", (text or "").lower()) if t and t not in STOPWORDS] + + +def haversine_miles(lat1: float, lng1: float, lat2: float, lng2: float) -> float: + radius = 3958.7613 + p1, p2 = math.radians(lat1), math.radians(lat2) + dp = p2 - p1 + dl = math.radians(lng2 - lng1) + a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 + return 2 * radius * math.asin(math.sqrt(a)) + + +def safe_next(raw: str | None) -> str | None: + """Only allow same-origin relative paths as a ?next= target.""" + if not raw: + return None + if not raw.startswith("/") or raw.startswith("//") or "\\" in raw: + return None + return raw + + +def resolve_location(raw: str) -> Store | None: + """Resolve free text against seeded stores: 'City, ST', 'City', 'ST' or a ZIP prefix.""" + text = (raw or "").strip() + if not text: + return None + stores = Store.query.order_by(Store.store_number).all() + digits = re.sub(r"[^0-9]", "", text) + if len(digits) >= 5: + for store in stores: + if store.zip.replace("-", "").startswith(digits[:5]): + return store + parts = [p.strip() for p in text.split(",") if p.strip()] + city = parts[0].lower() if parts else "" + state = parts[1].upper()[:2] if len(parts) > 1 else "" + if state: + for store in stores: + if store.city.lower() == city and store.state == state: + return store + for store in stores: + if store.city.lower() == city: + return store + if len(text) == 2: + for store in stores: + if store.state == text.upper(): + return store + return None + + +def current_filters() -> dict: + """Read the results-page query string into a normalised dict.""" + args = request.args + query = (args.get("q") or args.get("searchQuery") or "").strip() + if query.lower() == "all": + query = "" + try: + page = max(1, int(args.get("page", "1"))) + except ValueError: + page = 1 + try: + radius = int(args.get("radius", "25")) + except ValueError: + radius = 25 + if radius not in RADIUS_VALUES: + radius = 25 + sort = args.get("sort", "relevance") + if sort not in SORT_VALUES: + sort = "relevance" + return { + "q": query, + "area": [v for v in args.getlist("area") if v], + "category": [v for v in args.getlist("category") if v], + "brand": [v for v in args.getlist("brand") if v in BRAND_VALUES], + "shift": [v for v in args.getlist("shift") if v in SHIFT_VALUES], + "type": [v for v in args.getlist("type") if v in EMPLOYMENT_TYPE_VALUES], + "rate": [v for v in args.getlist("rate") if v in RATE_VALUES], + "loc": (args.get("loc") or "").strip(), + "radius": radius, + "sort": sort, + "page": page, + "tab": args.get("tab", "jobs"), + } + + +def filters_query(filters: dict, **overrides) -> str: + merged = dict(filters) + merged.update(overrides) + pairs: list[tuple[str, str]] = [] + if merged.get("q"): + pairs.append(("q", merged["q"])) + for key in ("area", "category", "brand", "shift", "type", "rate"): + for value in merged.get(key) or []: + pairs.append((key, value)) + if merged.get("loc"): + pairs.append(("loc", merged["loc"])) + pairs.append(("radius", str(merged.get("radius", 25)))) + if merged.get("sort") and merged["sort"] != "relevance": + pairs.append(("sort", merged["sort"])) + if merged.get("tab") and merged["tab"] != "jobs": + pairs.append(("tab", merged["tab"])) + if merged.get("page", 1) and int(merged.get("page", 1)) > 1: + pairs.append(("page", str(merged["page"]))) + return urlencode(pairs) + + +def _stem_match(token: str, blob_tokens: set[str]) -> bool: + """Loose match so 'optician' also surfaces 'Optical Services' rows. + + Two words match when they share a prefix of at least five characters; the + search is scored, never a strict AND, so this only widens the result set. + """ + if len(token) < 5: + return False + for other in blob_tokens: + if len(other) < 5: + continue + limit = min(len(token), len(other)) + shared = 0 + while shared < limit and token[shared] == other[shared]: + shared += 1 + if shared >= 5: + return True + return False + + +def score_job(job: Job, tokens: list[str]) -> float: + if not tokens: + return 0.0 + blob = job.search_blob.lower() + blob_tokens = {t for t in re.split(r"[^a-z0-9']+", blob) if t} + score = 0.0 + for token in tokens: + if token in blob_tokens: + score += 2.0 + elif token in blob: + score += 1.0 + elif _stem_match(token, blob_tokens): + score += 0.5 + title_tokens = {t for t in re.split(r"[^a-z0-9']+", job.title.lower()) if t} + for token in tokens: + if token in title_tokens: + score += 1.5 + return score + + +def search_jobs(filters: dict) -> tuple[list[Job], Store | None, bool]: + """Return (ordered jobs, resolved location store, location_failed).""" + jobs = Job.query.order_by(Job.job_id).all() + location_store = None + location_failed = False + if filters["loc"]: + location_store = resolve_location(filters["loc"]) + location_failed = location_store is None + + if filters["brand"]: + jobs = [j for j in jobs if j.brand in filters["brand"]] + if filters["type"]: + jobs = [j for j in jobs if j.employment_type in filters["type"]] + if filters["rate"]: + wanted = {"Salaried": "salaried", "Hourly": "hourly"} + allowed = {wanted[r] for r in filters["rate"]} + jobs = [j for j in jobs if j.population in allowed] + if filters["shift"]: + wanted_shifts = set(filters["shift"]) + jobs = [j for j in jobs if wanted_shifts & set(j.shifts)] + if filters["area"]: + wanted_areas = {a.lower() for a in filters["area"]} + jobs = [ + j for j in jobs + if j.area and (j.area.slug.lower() in wanted_areas or j.area.name.lower() in wanted_areas) + ] + if filters["category"]: + wanted_cats = {c.lower() for c in filters["category"]} + jobs = [ + j for j in jobs + if j.category and (j.category.slug.lower() in wanted_cats or j.category.name.lower() in wanted_cats) + ] + if location_store is not None: + radius = filters["radius"] + jobs = [ + j for j in jobs + if haversine_miles(location_store.lat, location_store.lng, j.store.lat, j.store.lng) <= radius + ] + + tokens = tokenize(filters["q"]) + if tokens: + scored = [(score_job(j, tokens), j) for j in jobs] + scored = [(s, j) for s, j in scored if s > 0] + if filters["sort"] == "most_recent": + scored.sort(key=lambda pair: (-pair[1].posted_date.toordinal(), pair[1].sort_rank)) + else: + scored.sort(key=lambda pair: (-pair[0], pair[1].sort_rank, pair[1].job_id)) + jobs = [j for _, j in scored] + else: + if filters["sort"] == "most_recent": + jobs.sort(key=lambda j: (-j.posted_date.toordinal(), j.sort_rank)) + else: + jobs.sort(key=lambda j: (j.sort_rank, j.job_id)) + return jobs, location_store, location_failed + + +def cluster_map_svg(jobs: list[Job], width: int = 520, height: int = 380) -> str: + """Deterministic server-rendered cluster map (no third-party map tiles).""" + lon_min, lon_max = -125.0, -65.0 + lat_min, lat_max = 17.0, 50.0 + pad = 12 + + def project(lat: float, lng: float) -> tuple[float, float]: + x = pad + (lng - lon_min) / (lon_max - lon_min) * (width - 2 * pad) + y = pad + (lat_max - lat) / (lat_max - lat_min) * (height - 2 * pad) + return round(x, 1), round(y, 1) + + def path_for(points: list[tuple[float, float]]) -> str: + coords = [project(lat, lng) for lng, lat in points] + head = f"M {coords[0][0]} {coords[0][1]}" + rest = " ".join(f"L {x} {y}" for x, y in coords[1:]) + return f"{head} {rest} Z" + + counts: dict[int, int] = {} + for job in jobs: + counts[job.store_id] = counts.get(job.store_id, 0) + 1 + bubbles = [] + for store_id, count in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])): + store = db.session.get(Store, store_id) + if store is None: + continue + x, y = project(store.lat, store.lng) + radius = 12 + min(14, count * 2) + bubbles.append((x, y, radius, count, f"{store.city}, {store.state}")) + + parts = [ + f'', + f'', + f'', + f'', + ] + for x, y, radius, count, label in bubbles: + parts.append( + f'{label}: {count} open roles' + f'' + f'{count}' + ) + parts.append( + f'Map data ©2026 Walmart Careers mirror' + ) + parts.append("") + return "".join(parts) + + +def pin_card_svg(store: Store, width: int = 300, height: int = 170) -> str: + """Small deterministic SVG pin card used on the job detail page.""" + return ( + f'' + f'' + f'' + f'' + f'' + f'' + f'' + f'' + f'{store.city}, {store.state}' + f'{store.zip}' + f'' + ) + + +def trending_jobs() -> list[Job]: + jobs = [db.session.get(Job, jid) for jid in content.TRENDING_JOB_IDS] + return [j for j in jobs if j is not None] + + +def related_jobs(job: Job, limit: int = 3) -> list[Job]: + rows = ( + Job.query.filter( + Job.category_id == job.category_id, + Job.job_id != job.job_id, + Job.store_id != job.store_id, + ) + .order_by(Job.sort_rank, Job.job_id) + .limit(limit) + .all() + ) + if len(rows) < limit: + extra = ( + Job.query.filter( + Job.area_id == job.area_id, + Job.job_id != job.job_id, + ~Job.job_id.in_([r.job_id for r in rows]), + ) + .order_by(Job.sort_rank, Job.job_id) + .limit(limit - len(rows)) + .all() + ) + rows = rows + extra + return rows + + +def saved_job_ids() -> set[str]: + if not current_user.is_authenticated: + return set() + return { + row.job_id + for row in SavedJob.query.filter_by(user_id=current_user.id).all() + } + + +@app.context_processor +def inject_globals(): + areas = ( + Area.query.filter_by(has_index_page=True) + .order_by(Area.display_order) + .all() + ) + return { + "nav_areas": areas, + "content": content, + "current_year": content.MIRROR_REFERENCE_DATE.year, + "search_q": (request.args.get("q") or request.args.get("searchQuery") or ""), + } + + +# --------------------------------------------------------------------------- # +# Routes +# --------------------------------------------------------------------------- # +@app.route("/home") +@app.route("/") +def index(): + areas = ( + Area.query.filter_by(has_index_page=True, is_filterable=True) + .order_by(Area.display_order) + .all() + ) + return render_template( + "index.html", + trending=trending_jobs(), + ribbon_areas=areas, + saved_ids=saved_job_ids(), + ) + + +@app.route("/results") +def results(): + filters = current_filters() + jobs, location_store, location_failed = search_jobs(filters) + total = len(jobs) + pages = max(1, math.ceil(total / PAGE_SIZE)) + page = min(filters["page"], pages) + filters["page"] = page + start = (page - 1) * PAGE_SIZE + page_jobs = jobs[start:start + PAGE_SIZE] + + areas = Area.query.filter_by(is_filterable=True).order_by(Area.display_order).all() + return render_template( + "results.html", + filters=filters, + jobs=page_jobs, + total=total, + page=page, + pages=pages, + areas=areas, + shift_values=SHIFT_VALUES, + brand_values=BRAND_VALUES, + employment_type_values=EMPLOYMENT_TYPE_VALUES, + rate_values=RATE_VALUES, + radius_values=RADIUS_VALUES, + location_store=location_store, + location_failed=location_failed, + map_svg=cluster_map_svg(jobs), + saved_ids=saved_job_ids(), + qs=filters_query, + ) + + +@app.route("/jobs/") +def job_detail(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + return render_template( + "job_detail.html", + job=job, + related=related_jobs(job), + is_saved=job.job_id in saved_job_ids(), + map_svg=pin_card_svg(job.store), + benefit_tiles=content.benefit_tiles_for(job.brand, job.population), + saved_ids=saved_job_ids(), + ) + + +@app.route("/jobs//save", methods=["POST"]) +def save_job(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + if not current_user.is_authenticated: + return redirect(url_for("login", next=url_for("job_detail", job_id=job_id))) + existing = SavedJob.query.filter_by(user_id=current_user.id, job_id=job_id).first() + if existing is None: + db.session.add( + SavedJob(user_id=current_user.id, job_id=job_id, saved_at=datetime.now()) + ) + db.session.commit() + flash(f"Saved {job.title} to your saved roles.", "success") + target = safe_next(request.form.get("next")) or url_for("job_detail", job_id=job_id) + return redirect(target) + + +@app.route("/jobs//unsave", methods=["POST"]) +def unsave_job(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + if not current_user.is_authenticated: + return redirect(url_for("login", next=url_for("saved_roles"))) + existing = SavedJob.query.filter_by(user_id=current_user.id, job_id=job_id).first() + if existing is not None: + db.session.delete(existing) + db.session.commit() + flash(f"Removed {job.title} from your saved roles.", "success") + target = safe_next(request.form.get("next")) or url_for("saved_roles") + return redirect(target) + + +@app.route("/jobs//apply", methods=["GET", "POST"]) +def apply_contact(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + errors: list[str] = [] + form = { + "email": "", + "first_name": "", + "last_name": "", + "phone": "", + } + if current_user.is_authenticated: + form.update( + { + "email": current_user.email, + "first_name": current_user.first_name, + "last_name": current_user.last_name, + "phone": current_user.phone, + } + ) + if request.method == "POST": + for key in form: + form[key] = (request.form.get(key) or "").strip() + agreed = request.form.get("terms") == "on" + if not EMAIL_PATTERN.fullmatch(form["email"]): + errors.append("Enter a valid email address.") + if not form["first_name"]: + errors.append("Enter your first name.") + if not form["last_name"]: + errors.append("Enter your last name.") + if len(re.sub(r"[^0-9]", "", form["phone"])) < 10: + errors.append("Enter a phone number with at least 10 digits.") + if not agreed: + errors.append("You must accept the Terms & Conditions to continue.") + if not errors: + session["apply_draft"] = {"job_id": job_id, **form} + return redirect(url_for("apply_confirm", job_id=job_id)) + return render_template("apply_contact.html", job=job, form=form, errors=errors) + + +@app.route("/jobs//apply/confirm", methods=["GET", "POST"]) +def apply_confirm(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + draft = session.get("apply_draft") + if not draft or draft.get("job_id") != job_id: + flash("Start your application by entering your contact details.", "warning") + return redirect(url_for("apply_contact", job_id=job_id)) + if request.method == "POST": + application = Application( + job_id=job_id, + user_id=current_user.id if current_user.is_authenticated else None, + email=draft["email"], + first_name=draft["first_name"], + last_name=draft["last_name"], + phone=draft["phone"], + status="Submitted", + confirmation_no="pending", + submitted_at=datetime.now(), + ) + db.session.add(application) + db.session.flush() + application.confirmation_no = confirmation_for(application.id) + db.session.commit() + session.pop("apply_draft", None) + session["apply_submitted_id"] = application.id + return redirect(url_for("apply_submitted", job_id=job_id)) + return render_template("apply_confirm.html", job=job, draft=draft) + + +@app.route("/jobs//apply/submitted") +def apply_submitted(job_id: str): + job = db.session.get(Job, job_id) + if job is None: + abort(404) + application_id = session.get("apply_submitted_id") + application = db.session.get(Application, application_id) if application_id else None + if application is None or application.job_id != job_id: + flash("We couldn't find that application. Please apply again.", "warning") + return redirect(url_for("apply_contact", job_id=job_id)) + return render_template("apply_submitted.html", job=job, application=application) + + +@app.route("/careers-areas/") +def career_area(slug: str): + area = Area.query.filter(db.func.lower(Area.slug) == slug.lower()).first() + if area is None or not area.has_index_page: + abort(404) + categories = ( + Category.query.filter_by(area_id=area.id) + .order_by(Category.display_order) + .all() + ) + counts = { + c.id: Job.query.filter_by(category_id=c.id).count() for c in categories + } + return render_template( + "area.html", + area=area, + categories=categories, + counts=counts, + hubs=Store.query.filter_by(is_hub=True).order_by(Store.id).all(), + ) + + +@app.route("/resources/location") +def resources_location(): + hubs = Store.query.filter_by(is_hub=True).order_by(Store.id).all() + counts = {s.id: Job.query.filter_by(store_id=s.id).count() for s in hubs} + return render_template("locations.html", hubs=hubs, counts=counts) + + +@app.route("/resources/hiring-process") +def resources_hiring(): + return render_template("hiring_process.html", trending=trending_jobs(), saved_ids=saved_job_ids()) + + +@app.route("/resources/terms-and-conditions") +def resources_terms(): + return render_template("terms.html") + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + next_url = safe_next(request.args.get("next")) + errors: list[str] = [] + email = "" + if request.method == "POST": + email = (request.form.get("email") or "").strip().lower() + password = request.form.get("password") or "" + next_url = safe_next(request.form.get("next")) or next_url + user = User.query.filter_by(email=email).first() + if user is None or not user.check_password(password): + errors.append("We couldn't sign you in with that email and password.") + else: + login_user(user) + flash(f"Signed in as {user.display_name}.", "success") + return redirect(next_url or url_for("index")) + return render_template("login.html", errors=errors, email=email, next_url=next_url) + + +@app.route("/register", methods=["GET", "POST"]) +def register(): + next_url = safe_next(request.args.get("next")) + errors: list[str] = [] + form = {"email": "", "first_name": "", "last_name": ""} + if request.method == "POST": + for key in form: + form[key] = (request.form.get(key) or "").strip() + password = request.form.get("password") or "" + confirm = request.form.get("confirm_password") or "" + next_url = safe_next(request.form.get("next")) or next_url + email = form["email"].lower() + if not EMAIL_PATTERN.fullmatch(email): + errors.append("Enter a valid email address.") + elif User.query.filter_by(email=email).first(): + errors.append("An account already exists for that email address.") + if len(password) < 8: + errors.append("Choose a password with at least 8 characters.") + if password != confirm: + errors.append("The two passwords don't match.") + if not form["first_name"]: + errors.append("Enter your first name.") + if not form["last_name"]: + errors.append("Enter your last name.") + if not errors: + display = f"{form['first_name']} {form['last_name']}".strip() + username = email.split("@")[0] + if User.query.filter_by(username=username).first(): + username = f"{username}.{User.query.count() + 1}" + user = User( + email=email, + username=username, + display_name=display, + first_name=form["first_name"], + last_name=form["last_name"], + phone="", + city="", + state="", + password_hash=generate_password_hash(password), + created_at=datetime.now(), + ) + db.session.add(user) + db.session.commit() + login_user(user) + flash("Your candidate account is ready.", "success") + return redirect(next_url or url_for("saved_roles")) + return render_template("register.html", errors=errors, form=form, next_url=next_url) + + +@app.route("/logout", methods=["POST"]) +@login_required +def logout(): + logout_user() + session.pop("apply_draft", None) + session.pop("apply_submitted_id", None) + flash("You have been signed out.", "info") + return redirect(url_for("index")) + + +@app.route("/account") +@login_required +def account(): + return render_template( + "account.html", + saved_count=SavedJob.query.filter_by(user_id=current_user.id).count(), + application_count=Application.query.filter_by(user_id=current_user.id).count(), + ) + + +@app.route("/account/edit", methods=["GET", "POST"]) +@login_required +def account_edit(): + errors: list[str] = [] + form = { + "display_name": current_user.display_name, + "first_name": current_user.first_name, + "last_name": current_user.last_name, + "phone": current_user.phone, + "city": current_user.city, + "state": current_user.state, + } + if request.method == "POST": + for key in form: + form[key] = (request.form.get(key) or "").strip() + form["state"] = form["state"].upper()[:2] + if not form["display_name"]: + errors.append("Enter a display name.") + if form["state"] and not re.fullmatch(r"[A-Z]{2}", form["state"]): + errors.append("Use a two-letter state code.") + if not errors: + for key, value in form.items(): + setattr(current_user, key, value) + db.session.commit() + flash("Your profile has been updated.", "success") + return redirect(url_for("account")) + return render_template("account_edit.html", form=form, errors=errors) + + +@app.route("/candidate-home/saved-roles") +def saved_roles(): + rows: list[SavedJob] = [] + if current_user.is_authenticated: + rows = ( + SavedJob.query.filter_by(user_id=current_user.id) + .order_by(SavedJob.saved_at.desc(), SavedJob.id.desc()) + .all() + ) + return render_template( + "saved_roles.html", + rows=rows, + trending=trending_jobs(), + saved_ids=saved_job_ids(), + ) + + +@app.route("/candidate-home/applications") +@login_required +def applications(): + rows = ( + Application.query.filter_by(user_id=current_user.id) + .order_by(Application.submitted_at.desc(), Application.id.desc()) + .all() + ) + return render_template("applications.html", rows=rows) + + +@app.route("/_health") +def health(): + return jsonify( + { + "ok": True, + "site": "walmart_careers", + "jobs": Job.query.count(), + "stores": Store.query.count(), + "areas": Area.query.count(), + "categories": Category.query.count(), + "users": User.query.count(), + } + ) + + +@app.errorhandler(404) +def not_found(_error): + return render_template("404.html"), 404 + + +@app.errorhandler(500) +def server_error(_error): # pragma: no cover - defensive + return render_template("404.html"), 500 + + +def bootstrap_site() -> None: + from seed_data import seed_benchmark_users, seed_database + + with app.app_context(): + db.create_all() + seed_database() + seed_benchmark_users() + + +# `python app.py` loads this file as __main__; register it under its import +# name too so seed_data's `from app import ...` reuses this module instead of +# building a second Flask app + SQLAlchemy instance. +sys.modules.setdefault("app", sys.modules[__name__]) + +if os.environ.get("WEBSYN_SKIP_BOOTSTRAP") != "1": + bootstrap_site() + + +if __name__ == "__main__": + port = int(os.environ.get("PORT", "5000")) + app.run(host="0.0.0.0", port=port, debug=False) diff --git a/sites/walmart_careers/catalog_source.py b/sites/walmart_careers/catalog_source.py new file mode 100644 index 00000000..715626a0 --- /dev/null +++ b/sites/walmart_careers/catalog_source.py @@ -0,0 +1,1932 @@ +"""Deterministic source catalog for the Walmart Careers mirror. + +Every posting in the mirror is generated from this file: title families with an +explicit list of placements (store, employment type, shifts, pay band, open +positions). Body copy comes from per-family templates with slot fills, so the +200 postings stay internally consistent without 200 hand-written essays. + +Nothing here is read at request time. `seed_data.py` turns it into SQLite rows. +""" +from __future__ import annotations + +# --------------------------------------------------------------------------- # +# Areas +# --------------------------------------------------------------------------- # +# (slug, name, display_order, blurb, hero_image, has_index_page, is_filterable) +AREAS = [ + ( + "stores-and-clubs", + "Stores and Clubs", + 1, + "Find your path with us. Whether you're interested in auto care, front-end services, " + "general merchandising, or another team, you'll find opportunities to grow and the " + "support to reach your career goals.", + "area-stores.jpg", + True, + True, + ), + ( + "supply-chain-and-transportation", + "Supply Chain and Transportation", + 2, + "Move product, move people forward. Our distribution centers, fulfillment centers and " + "private fleet keep shelves stocked and orders on time across the country.", + "area-supply-chain.jpg", + True, + True, + ), + ( + "healthcare", + "Healthcare", + 3, + "Care that reaches everyone. Our pharmacy, vision and wellness teams serve millions of " + "neighbours every week, right inside the stores they already shop.", + "area-healthcare.jpg", + True, + True, + ), + ( + "technology", + "Technology", + 4, + "At Walmart and Sam's Club, we are people-led, tech-powered. Everything you build here - " + "from smarter supply chains to seamless shopping - starts with real needs and creates " + "real impact.", + "area-technology.jpg", + True, + True, + ), + ( + "corporate", + "Corporate", + 5, + "Strategy, finance, merchandising, marketing and people teams that set the direction for " + "the world's largest retailer.", + "area-corporate.jpg", + True, + True, + ), + ( + "students", + "Students", + 6, + "Internships and early career programs across every part of the business.", + "area-students.png", + False, + True, + ), + ( + "Military", + "Military", + 7, + "Your service prepared you to lead. Bring that experience to a company that hires " + "thousands of veterans, transitioning service members and military spouses every year.", + "area-military.jpg", + True, + False, + ), +] + +# (area_slug, category_name, category_slug, display_order) +CATEGORIES = [ + ("stores-and-clubs", "Cashier and Front-End Services", "cashier-and-front-end-services", 1), + ("stores-and-clubs", "Food and Grocery", "food-and-grocery", 2), + ("stores-and-clubs", "General Merchandise, Stocking, and Unloading", "general-merchandise-stocking-and-unloading", 3), + ("stores-and-clubs", "Digital Pickup and Delivery", "digital-pickup-and-delivery", 4), + ("stores-and-clubs", "Cafe", "cafe", 5), + ("stores-and-clubs", "Retail Management", "retail-management", 6), + ("stores-and-clubs", "Fuel Station", "fuel-station", 7), + ("stores-and-clubs", "Auto Care Center", "auto-care-center", 8), + ("stores-and-clubs", "Auto Services", "auto-services", 9), + ("stores-and-clubs", "Maintenance", "maintenance", 10), + ("stores-and-clubs", "Security and Asset Protection", "security-and-asset-protection", 11), + ("supply-chain-and-transportation", "SC&T Operations", "sct-operations", 1), + ("supply-chain-and-transportation", "Drivers", "drivers", 2), + ("supply-chain-and-transportation", "Engineering", "engineering", 3), + ("supply-chain-and-transportation", "Aviation", "aviation", 4), + ("supply-chain-and-transportation", "Security and Asset Protection", "sct-security-and-asset-protection", 5), + ("healthcare", "Pharmacy Services", "pharmacy-services", 1), + ("healthcare", "Optical Services", "optical-services", 2), + ("healthcare", "Health and Wellness Operations", "health-and-wellness-operations", 3), + ("healthcare", "Clinical Care", "clinical-care", 4), + ("technology", "Software Engineering and Architecture", "software-engineering-and-architecture", 1), + ("technology", "Product Management", "product-management", 2), + ("technology", "Data Science and Analytics", "data-science-and-analytics", 3), + ("technology", "Information Security", "information-security", 4), + ("technology", "Creative Design and UX", "creative-design-and-ux", 5), + ("technology", "Technical Program Management", "technical-program-management", 6), + ("technology", "Information Technology", "information-technology", 7), + ("corporate", "Accounting and Finance", "accounting-and-finance", 1), + ("corporate", "Human Resources", "human-resources", 2), + ("corporate", "Marketing and Advertising", "marketing-and-advertising", 3), + ("corporate", "Merchandising", "merchandising", 4), + ("corporate", "Business Operations", "business-operations", 5), + ("students", "Internship", "internship", 1), +] + +# --------------------------------------------------------------------------- # +# Stores (store_number, banner, location_name, street, city, state, zip, +# lat, lng, is_hub, is_office, brand) +# --------------------------------------------------------------------------- # +STORES = [ + # --- offices ----------------------------------------------------------- + ("10101", "Home Office", "WALMART HOME OFFICE", "702 SW 8th St", "Bentonville", "AR", "72716-0000", 36.363430, -94.219970, True, True, "Walmart"), + ("11807", "Home Office", "SUNNYVALE TECH CORNERS BLDG 6", "811 11th Ave", "Sunnyvale", "CA", "94089-4731", 37.402869, -122.036132, True, True, "Walmart"), + ("11003", "Home Office", "HOBOKEN TECH HUB", "221 River St", "Hoboken", "NJ", "07030-5989", 40.735657, -74.030324, True, True, "Walmart"), + ("11500", "Home Office", "DALLAS METRO OFFICE", "603 Munger Ave", "Dallas", "TX", "75202-3505", 32.784618, -96.796851, True, True, "Walmart"), + ("11109", "Home Office", "SAM'S CLUB HOME OFFICE", "2101 SE Simple Savings Dr", "Bentonville", "AR", "72712-4304", 36.343100, -94.196000, False, True, "Sam's Club"), + ("12200", "Vizio Campus", "VIZIO IRVINE CAMPUS", "39 Tesla", "Irvine", "CA", "92618-4603", 33.650800, -117.744400, False, True, "Vizio"), + # --- Arkansas ---------------------------------------------------------- + ("5260", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5260", "1400 SE Walton Blvd", "Bentonville", "AR", "72712-6220", 36.354900, -94.202500, False, False, "Walmart"), + ("144", "WM Supercenter", "WM SUPERCENTER #144", "2110 W Walnut St", "Rogers", "AR", "72756-3611", 36.334100, -94.152800, False, False, "Walmart"), + ("8259", "Sam's Club", "SAM'S CLUB #8259", "1101 SE Walton Blvd", "Bentonville", "AR", "72712-6191", 36.357800, -94.199200, False, False, "Sam's Club"), + # --- California -------------------------------------------------------- + ("9054", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9054", "1290 W Henderson Ave", "Porterville", "CA", "93257-5969", 36.070300, -119.041800, False, False, "Walmart"), + ("2050", "WM Supercenter", "WM SUPERCENTER #2050", "3680 W Shaw Ave", "Fresno", "CA", "93711-3204", 36.808900, -119.828600, False, False, "Walmart"), + ("6608", "Sam's Club", "SAM'S CLUB #6608", "5205 Monterey Hwy", "San Jose", "CA", "95111-4106", 37.259700, -121.816200, False, False, "Sam's Club"), + # --- New Jersey -------------------------------------------------------- + ("2110", "WM Supercenter", "WM SUPERCENTER #2110", "400 Park Plaza Dr", "Secaucus", "NJ", "07094-3661", 40.786600, -74.061800, False, False, "Walmart"), + ("3520", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #3520", "2100 88th St", "North Bergen", "NJ", "07047-4720", 40.792400, -74.011200, False, False, "Walmart"), + # --- Texas ------------------------------------------------------------- + ("9399", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9399", "3401 Quincy St", "Plainview", "TX", "79072-3308", 34.164300, -101.700900, False, False, "Walmart"), + ("4750", "Sam's Club", "SAM'S CLUB #4750", "3000 E Plano Pkwy", "Plano", "TX", "75074-7440", 33.017200, -96.671900, False, False, "Sam's Club"), + ("471", "WM Supercenter", "WM SUPERCENTER #471", "4215 Canyon Dr", "Amarillo", "TX", "79110-1109", 35.166900, -101.850700, False, False, "Walmart"), + # --- Florida ----------------------------------------------------------- + ("3387", "WM Supercenter", "WM SUPERCENTER #3387", "17000 Toledo Blade Blvd", "North Port", "FL", "34287-7281", 27.056300, -82.183200, False, False, "Walmart"), + ("6318", "Sam's Club", "SAM'S CLUB #6318", "4763 Millenia Plaza Way", "Orlando", "FL", "32839-6014", 28.485600, -81.430200, False, False, "Sam's Club"), + ("7133", "Regional DC", "REGIONAL DISTRIBUTION CENTER #7133", "3001 Bartow Rd", "Lakeland", "FL", "33803-6413", 27.981100, -81.930100, False, False, "Walmart"), + # --- Ohio -------------------------------------------------------------- + ("2073", "WM Supercenter", "WM SUPERCENTER #2073", "10000 Brookpark Rd", "Cleveland", "OH", "44130-1102", 41.409300, -81.786600, False, False, "Walmart"), + ("5388", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5388", "6594 Ridge Rd", "Parma", "OH", "44129-5546", 41.387000, -81.748000, False, False, "Walmart"), + ("6636", "Sam's Club", "SAM'S CLUB #6636", "3950 W Dublin Granville Rd", "Columbus", "OH", "43235-2701", 40.098700, -83.083100, False, False, "Sam's Club"), + ("5439", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5439", "5821 W Central Ave", "Toledo", "OH", "43615-2159", 41.673900, -83.673400, False, False, "Walmart"), + # --- New York ---------------------------------------------------------- + ("9046", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9046", "8827 Old River Rd", "Marcy", "NY", "13403-3030", 43.173965, -75.315183, False, False, "Walmart"), + ("6038", "Regional DC", "REGIONAL DISTRIBUTION CENTER #6038", "5000 Halsey Rd", "Marcy", "NY", "13403-2317", 43.155900, -75.297400, False, False, "Walmart"), + ("2163", "WM Supercenter", "WM SUPERCENTER #2163", "1490 Hudson Ave", "Rochester", "NY", "14621-2404", 43.201800, -77.586300, False, False, "Walmart"), + # --- Kansas ------------------------------------------------------------ + ("5991", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5991", "2441 S Rock Rd", "Wichita", "KS", "67207-3254", 37.653900, -97.240100, False, False, "Walmart"), + ("1179", "WM Supercenter", "WM SUPERCENTER #1179", "1301 SW Wanamaker Rd", "Topeka", "KS", "66604-3843", 39.032200, -95.762700, False, False, "Walmart"), + ("6014", "Regional DC", "REGIONAL DISTRIBUTION CENTER #6014", "2101 S Princeton St", "Ottawa", "KS", "66067-8501", 38.588600, -95.263700, False, False, "Walmart"), + # --- Mississippi ------------------------------------------------------- + ("954", "WM Supercenter", "WM SUPERCENTER #954", "1266 Highway 51 N", "Hazlehurst", "MS", "39083-2217", 31.879400, -90.397300, False, False, "Walmart"), + ("1230", "WM Supercenter", "WM SUPERCENTER #1230", "1130 Brookway Blvd", "Brookhaven", "MS", "39601-3211", 31.556600, -90.443800, False, False, "Walmart"), + ("8253", "Sam's Club", "SAM'S CLUB #8253", "6360 Ridgewood Ct Dr", "Jackson", "MS", "39211-3520", 32.393200, -90.140800, False, False, "Sam's Club"), + # --- Iowa -------------------------------------------------------------- + ("9281", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9281", "2600 Iris Rd", "Mount Pleasant", "IA", "52641-3106", 40.966400, -91.549600, False, False, "Walmart"), + ("1236", "WM Supercenter", "WM SUPERCENTER #1236", "5101 SE 14th St", "Des Moines", "IA", "50320-2201", 41.531700, -93.596800, False, False, "Walmart"), + # --- Washington -------------------------------------------------------- + ("4137", "WM Supercenter", "WM SUPERCENTER #4137", "1965 S Union Ave", "Tacoma", "WA", "98405-1615", 47.242300, -122.484500, False, False, "Walmart"), + ("6216", "Sam's Club", "SAM'S CLUB #6216", "9950 N Newport Hwy", "Spokane", "WA", "99218-1240", 47.741400, -117.400600, False, False, "Sam's Club"), + ("5382", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5382", "8102 Evergreen Way", "Everett", "WA", "98203-6428", 47.905400, -122.229900, False, False, "Walmart"), + # --- Puerto Rico ------------------------------------------------------- + ("2503", "WM Supercenter", "WM SUPERCENTER #2503", "Carr 2 KM 11.4", "Bayamon", "PR", "00959-5100", 18.394200, -66.155300, False, False, "Walmart"), + ("2610", "WM Supercenter", "WM SUPERCENTER #2610", "500 Ave Rafael Cordero", "Caguas", "PR", "00725-3607", 18.245600, -66.036200, False, False, "Walmart"), + ("3512", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #3512", "2000 Ave Las Americas", "Ponce", "PR", "00717-0777", 18.019800, -66.612600, False, False, "Walmart"), + ("8763", "Sam's Club", "SAM'S CLUB #8763", "100 Ave Fragoso", "Carolina", "PR", "00979-1234", 18.417400, -65.977300, False, False, "Sam's Club"), + # --- Virginia ---------------------------------------------------------- + ("1399", "WM Supercenter", "WM SUPERCENTER #1399", "1123 E Lynchburg Salem Tpke", "Bedford", "VA", "24523-3446", 37.323200, -79.502400, False, False, "Walmart"), + ("6088", "Import", "IMPORT DISTRIBUTION CENTER #6088", "8109 Merrimac Trail", "Williamsburg", "VA", "23185-6255", 37.288600, -76.664900, False, False, "Walmart"), +] + +# --------------------------------------------------------------------------- # +# Shifts +# --------------------------------------------------------------------------- # +SHIFT_CODES = { + "WD": "Weekday Day", + "WE": "Weekday Evening", + "WN": "Weekday Overnight", + "SD": "Weekend Day", + "SE": "Weekend Evening", + "SN": "Weekend Overnight", + "FX": "Flex", +} +SHIFT_WINDOWS = { + "WD": "Shift may start between 6:00am - 11:00am", + "WE": "Shift may start between 12:00pm - 5:00pm", + "WN": "Shift may start between 8:00pm - 1:00am", + "SD": "Shift may start between 5:00am - 10:00am", + "SE": "Shift may start between 1:00pm - 6:00pm", + "SN": "Shift may start between 6:00pm - 3:00am", + "FX": "Shift may start between 7:00am - 7:00pm", +} + + +# --------------------------------------------------------------------------- # +# Hourly title families. +# +# placement tuple: (store_number, employment_type, shift_codes, min_pay, +# max_pay, positions_available, extras) +# `extras` is an optional dict: {"job_id": ..., "shift_time": ..., "min_age": bool} +# Body copy is a per-family template; slots are {banner} {store} {city} {state}. +# --------------------------------------------------------------------------- # +HOURLY_FAMILIES = [ + { + "title": "Freight Handler", + "area": "supply-chain-and-transportation", + "category": "SC&T Operations", + "hashtag": "#supplychainjobs", + "summary": "Career opportunities in Freight Handling roles include Receiving, Unloading, " + "Processing, Orderfilling and Shipping.", + "do": [ + "As a Freight Handler at {banner} #{store} in {city}, {state}, you will have a critical role " + "in moving product through our supply chain network to the stores that serve our customers. " + "Your role is critical in providing our customers with the product they expect at an everyday " + "low price.", + "You can expect the work to be very physically demanding with an extremely high focus on your " + "safety and the safety of others. You will be lifting heavy cases in a climate-controlled and, " + "at times, non-climate-controlled environment. The flow of freight is very fast-paced and " + "productivity expectations are high.", + ], + "bring": [ + "Unload, sort and stage inbound freight using powered industrial equipment after certification.", + "Scan and verify case counts against the trailer manifest and flag discrepancies to the area coach.", + "Maintain a clean and safe work area, following all lockout/tagout and PPE requirements.", + "Complies with company policies, procedures, and standards of ethics and integrity. Performs " + "additional duties as assigned.", + ], + "placements": [ + ("9054", "Full time", "SN", 21.80, 25.30, 3, {"shift_time": "Shift may start between 6:00pm - 3:00am"}), + ("9399", "Part time", "SE", 18.50, 22.00, 2, None), + ("9046", "Full time", "WN", 20.90, 24.40, 4, {"shift_time": "Shift may start between 9:00pm - 1:30am"}), + ("6038", "Full time", "WE", 19.75, 23.25, 2, {"shift_time": "Shift may start between 3:00pm - 7:30pm"}), + ("9281", "Part time", "WD", 18.90, 22.40, 3, None), + ("6014", "Part time", "SD", 19.40, 22.90, 5, None), + ], + }, + { + "title": "eCom Warehouse Worker", + "area": "supply-chain-and-transportation", + "category": "SC&T Operations", + "hashtag": "#supplychainjobs", + "summary": "Pick, pack and ship the online orders that customers are waiting on, inside one of " + "our fulfillment buildings.", + "do": [ + "As an eCom Warehouse Worker at {banner} #{store} in {city}, {state}, you pick customer orders " + "from bins and totes, pack them to our quality standard, and hand them to the outbound dock so " + "they ship the same night.", + "Expect a fast, metrics-driven floor. You will stand and walk for most of your shift, lift up to " + "50 pounds, and rotate across pick, pack and ship stations as volume moves.", + ], + "bring": [ + "Pick and pack customer orders to the published units-per-hour standard.", + "Use a handheld scanner and the warehouse management system to confirm every unit.", + "Report damaged product and inventory discrepancies before the order leaves the building.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("9046", "Part time", "SN", 21.35, 24.85, 2, {"job_id": "CP-9046-11101", "shift_time": "Shift may start between 6:00pm - 2:30am"}), + ("9054", "Part time", "WN", 20.60, 24.10, 1, None), + ("9281", "Part time", "SD,WN", 19.20, 22.70, 3, None), + ("7133", "Full time", "WD", 18.80, 22.30, 4, None), + ], + }, + { + "title": "Order Filler", + "area": "supply-chain-and-transportation", + "category": "SC&T Operations", + "hashtag": "#supplychainjobs", + "summary": "Build store-ready pallets from the pick line and stage them for the outbound fleet.", + "do": [ + "Order Fillers at {banner} #{store} in {city}, {state} select cases from the pick line, build " + "stable pallets to the store's plan-o-gram sequence, wrap them, and stage them at the outbound " + "door for the driver.", + "You will use a rider pallet jack and a voice-directed pick system. Accuracy targets and case " + "rates are published daily and reviewed with your coach each week.", + ], + "bring": [ + "Select cases accurately using a voice-directed picking headset.", + "Build and wrap pallets that travel safely without shifting.", + "Operate a rider pallet jack after completing on-site certification.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6014", "Full time", "WN,FX,SN", 19.10, 22.60, 4, None), + ("7133", "Part time", "SE,FX", 18.40, 21.90, 2, None), + ("6038", "Part time", "WD,FX", 20.10, 23.60, 3, None), + ("9399", "Full time", "SN,FX", 19.85, 23.35, 2, None), + ], + }, + { + "title": "Yard Driver-Off Property", + "area": "supply-chain-and-transportation", + "category": "Drivers", + "hashtag": "#supplychainjobs", + "summary": "Move trailers between the yard, the dock doors and nearby off-property lots.", + "do": [ + "Yard Drivers at {banner} #{store} in {city}, {state} shuttle trailers between dock doors, the " + "on-site yard and nearby off-property parking so that inbound and outbound freight never waits " + "on a door.", + "You will spend the shift in a yard tractor, outdoors in all weather, coordinating over radio " + "with the dock office and the guard shack.", + ], + "bring": [ + "Hold a valid Class A commercial driver's license with a clean motor vehicle record.", + "Spot and pull trailers safely in tight yard conditions, day or night.", + "Complete yard checks and record trailer locations in the yard management system.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6088", "Part time", "SE", 22.25, 25.75, 2, None), + ("7133", "Part time", "WD", 23.10, 26.60, 1, None), + ("6014", "Full time", "WN", 22.80, 26.30, 3, None), + ("9399", "Part time", "FX,SN", 21.90, 25.40, 2, None), + ], + }, + { + "title": "Class A CDL Truck Driver", + "area": "supply-chain-and-transportation", + "category": "Drivers", + "hashtag": "#drivewithwalmart", + "summary": "Run scheduled store deliveries out of a private fleet transportation office.", + "do": [ + "Drivers based at {banner} #{store} in {city}, {state} run scheduled routes to stores and clubs " + "in the surrounding region, unloading with the store team and returning with backhaul freight.", + "Our private fleet runs newer equipment, publishes routes in advance and gets most drivers home " + "regularly. Safety scorecards are reviewed with your transportation manager every month.", + ], + "bring": [ + "Hold a valid Class A CDL and meet all federal Department of Transportation requirements.", + "At least 30 months of experience in the last 4 years driving a tractor trailer.", + "No preventable accidents or serious traffic violations in the last three years.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6038", "Full time", "WD", 32.00, 41.00, 6, None), + ("6014", "Full time", "SD,WN", 31.50, 40.50, 4, None), + ("7133", "Full time", "WE", 30.75, 39.75, 5, None), + ("6088", "Full time", "WN", 33.25, 42.25, 3, None), + ], + }, + { + "title": "Asset Protection Associate - All DC/FC", + "area": "supply-chain-and-transportation", + "category": "Security and Asset Protection", + "hashtag": "#supplychainjobs", + "summary": "Protect people, product and property inside a distribution or fulfillment building.", + "do": [ + "Asset Protection Associates at {banner} #{store} in {city}, {state} control access at the guard " + "shack and associate entrances, audit trailer seals, and run the camera system that covers the " + "dock and the yard.", + "You will partner with operations leadership on safety walks, investigate shrink incidents, and " + "write up findings for the asset protection manager.", + ], + "bring": [ + "Control access to the building and the yard, verifying credentials at every entry point.", + "Audit inbound and outbound trailer seals against the manifest.", + "Monitor camera systems and document incidents accurately and promptly.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("9046", "Part time", "WD,FX", 22.00, 25.50, 2, None), + ("9054", "Part time", "SD,FX", 21.00, 24.50, 1, None), + ("6088", "Full time", "WN", 22.50, 26.00, 2, None), + ("7133", "Full time", "SN,FX", 23.00, 26.50, 1, None), + ("6014", "Part time", "WE", 20.75, 24.25, 3, None), + ], + }, + { + "title": "Facility Maintenance Technician", + "area": "supply-chain-and-transportation", + "category": "Engineering", + "hashtag": "#supplychainjobs", + "summary": "Keep conveyors, dock equipment and building systems running across the shift.", + "do": [ + "Facility Maintenance Technicians at {banner} #{store} in {city}, {state} perform preventive " + "maintenance and emergency repairs on conveyor systems, sortation equipment, dock levellers and " + "building services.", + "You will read schematics, troubleshoot electrical and mechanical faults, and close out work " + "orders in the maintenance system before the end of your shift.", + ], + "bring": [ + "Two years of industrial maintenance experience or a completed technical program.", + "Troubleshoot 480V three-phase systems, motor controls and pneumatics safely.", + "Read and work from electrical, mechanical and pneumatic schematics.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("9046", "Full time", "WD,FX", 26.50, 34.00, 2, None), + ("9281", "Full time", "WN,FX", 25.75, 33.25, 1, None), + ("6038", "Full time", "SD", 27.00, 34.50, 2, None), + ("7133", "Full time", "WE,FX", 26.00, 33.50, 3, None), + ("6014", "Part time", "FX", 25.25, 32.75, 1, None), + ], + }, + { + "title": "Automation Technician", + "area": "supply-chain-and-transportation", + "category": "Engineering", + "hashtag": "#supplychainjobs", + "summary": "Support the robotics and controls that run our automated storage and retrieval systems.", + "do": [ + "Automation Technicians at {banner} #{store} in {city}, {state} maintain the robotics cells, " + "programmable controllers and vision systems behind our automated storage and retrieval " + "operation.", + "You will run diagnostics from the controls HMI, replace failed drives and sensors, and escalate " + "recurring faults to the controls engineering team with the data to back it up.", + ], + "bring": [ + "Experience maintaining PLC-controlled equipment, servo drives and industrial networks.", + "Comfort working at height and in confined maintenance aisles under lockout/tagout.", + "Track fault history and parts usage in the maintenance management system.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("9054", "Full time", "WN", 28.00, 36.00, 2, None), + ("9399", "Full time", "SN", 27.50, 35.50, 1, None), + ("6088", "Full time", "WD", 29.00, 37.00, 2, None), + ], + }, + { + "title": "Aviation Line Service Technician", + "area": "supply-chain-and-transportation", + "category": "Aviation", + "hashtag": "#supplychainjobs", + "summary": "Fuel, tow and service company aircraft on the ramp at a fleet operations base.", + "do": [ + "Line Service Technicians supporting {banner} #{store} in {city}, {state} marshal, fuel, tow and " + "de-ice company aircraft, and keep the ramp and hangar to airfield standard.", + "You will work directly with flight crews and the maintenance team, following company and FAA " + "ground handling procedures on every movement.", + ], + "bring": [ + "Ramp, fueling or ground handling experience at a fixed base operator or airline.", + "Valid driver's license and the ability to obtain an airport security badge.", + "Careful documentation of every fuel load and aircraft movement.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6014", "Full time", "WD", 24.00, 31.00, 1, None), + ("7133", "Part time", "SD", 23.50, 30.50, 1, None), + ("6088", "Part time", "WE", 22.75, 29.75, 2, None), + ("6038", "Full time", "FX,SN", 24.50, 31.50, 1, None), + ], + }, + { + "title": "Inventory Control Clerk", + "area": "supply-chain-and-transportation", + "category": "SC&T Operations", + "hashtag": "#supplychainjobs", + "summary": "Own cycle counts, research and the paperwork that keeps building inventory accurate.", + "do": [ + "Inventory Control Clerks at {banner} #{store} in {city}, {state} run daily cycle counts, " + "research variances between the system and the slot, and correct records so the building ships " + "what the store ordered.", + "Provides clerical and administrative support through generating and maintaining forms, reports " + "and logs via computerized management software, and communicating with the operations team on " + "open research.", + ], + "bring": [ + "Clerical duties (filing, keying, faxing), entering and extracting data from multiple systems.", + "Use of computer applications required (email, spreadsheets, word processing, and Microsoft Office).", + "The ability to be accurate and focus on attention to details will be critical.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("9281", "Part time", "WD,FX", 19.00, 22.50, 2, None), + ("9399", "Part time", "WE,FX", 19.60, 23.10, 1, None), + ("9046", "Full time", "SE,FX", 20.25, 23.75, 2, None), + ], + }, + # ----------------------------- Stores and Clubs ------------------------ + { + "title": "Cashier & Front End Services", + "area": "stores-and-clubs", + "category": "Cashier and Front-End Services", + "hashtag": "#storejobs", + "summary": "Greet members and customers at the front end, ring transactions and keep lines moving.", + "do": [ + "At {banner} #{store} in {city}, {state} you are the last person a customer sees, so you set the " + "tone for the whole trip. You ring up orders quickly and accurately, bag with care, and answer " + "questions about returns, pickup and our app.", + "You will rotate across registers, self checkout and the service desk depending on the hour, and " + "you will be on your feet for most of the shift.", + ], + "bring": [ + "Ring transactions accurately and handle cash, cards and digital tenders.", + "Support self checkout, resolving item and payment issues for customers.", + "Process returns and exchanges at the service desk to policy.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2503", "Part time", "WD,SD", 15.00, 24.00, 5, None), + ("2610", "Full time", "WD,WE,SD", 15.00, 24.00, 3, None), + ("3512", "Part time", "SE,SN", 15.00, 23.00, 4, None), + ("8763", "Part time", "WD,SN", 16.00, 25.00, 2, None), + ("2110", "Full time", "WD,WE", 15.50, 26.00, 4, None), + ("5382", "Part time", "WE,SE", 17.00, 28.00, 3, None), + ], + }, + { + "title": "Cosmetics Cashier", + "area": "stores-and-clubs", + "category": "Cashier and Front-End Services", + "hashtag": "#storejobs", + "summary": "Run the beauty counter register and keep the cosmetics department shoppable.", + "do": [ + "The cosmetics counter at {banner} #{store} in {city}, {state} has its own register and its own " + "regulars. You ring transactions there, help customers find shades and brands, and keep the " + "planogram faced and stocked.", + "You will also handle the department's security cases and coordinate restock with the general " + "merchandise team.", + ], + "bring": [ + "Ring transactions at a departmental register and reconcile the till at shift end.", + "Keep the beauty planogram faced, stocked and free of expired product.", + "Open locked cases for customers and follow high-theft merchandise procedures.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2503", "Part time", "WD,WE", 15.00, 24.00, 1, None), + ("3520", "Part time", "SD,SE", 15.00, 24.00, 2, None), + ("2163", "Part time", "WE,SE", 15.00, 25.00, 1, None), + ], + }, + { + "title": "Member Services Associate", + "area": "stores-and-clubs", + "category": "Cashier and Front-End Services", + "hashtag": "#samsclubjobs", + "summary": "Sign up new members, renew memberships and solve problems at the member services desk.", + "do": [ + "At {banner} #{store} in {city}, {state} you own the member services desk: new sign-ups, " + "renewals, upgrades, returns and the occasional tough conversation.", + "You will explain plan tiers and instant savings honestly, resolve billing questions, and hand " + "off anything you cannot fix to the club lead with the full picture.", + ], + "bring": [ + "Enroll, renew and upgrade memberships accurately in the membership system.", + "Process returns and refunds within club policy.", + "Explain plan benefits clearly without overselling.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("8259", "Full time", "WD,SD", 18.00, 26.00, 2, None), + ("6318", "Part time", "WE,SE", 16.00, 24.00, 3, None), + ("8763", "Full time", "WD,WE", 16.00, 24.00, 1, None), + ], + }, + { + "title": "Food & Grocery Associate", + "area": "stores-and-clubs", + "category": "Food and Grocery", + "hashtag": "#storejobs", + "summary": "Stock, rotate and merchandise the grocery aisles, coolers and freezers.", + "do": [ + "Food & Grocery Associates at {banner} #{store} in {city}, {state} unload the grocery truck, " + "stock dry, chilled and frozen departments, rotate dated product, and zone the aisles so the " + "store is shoppable at open.", + "You will handle food safety checks on your section and pull anything past code before it " + "reaches a customer.", + ], + "bring": [ + "Stock and rotate product following first-in, first-out standards.", + "Complete temperature and date checks for chilled and frozen sections.", + "Operate a manual and electric pallet jack after certification.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2110", "Full time", "WD,WE", 15.50, 26.00, 3, None), + ("2050", "Part time", "WE,SE", 17.50, 28.00, 2, None), + ("471", "Full time", "WD,SD", 15.00, 25.00, 4, None), + ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, None), + ("5388", "Full time", "WN", 16.50, 27.00, 2, None), + ], + }, + { + "title": "Freezer/Cooler Associate", + "area": "stores-and-clubs", + "category": "Food and Grocery", + "hashtag": "#samsclubjobs", + "summary": "Work the club's freezer and cooler boxes, stocking bulk frozen and chilled product.", + "do": [ + "At {banner} #{store} in {city}, {state} you spend most of the shift inside the freezer and " + "cooler boxes, breaking down pallets of bulk frozen and chilled product and building the club " + "floor displays.", + "Cold weather gear is provided. You will follow scheduled warm-up breaks and log box " + "temperatures every rotation.", + ], + "bring": [ + "Work extended periods in temperatures as low as minus ten degrees Fahrenheit.", + "Break down pallets and build club floor displays to the merchandising plan.", + "Record freezer and cooler temperatures on the posted schedule.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6608", "Full time", "WD,WN", 19.00, 27.00, 1, None), + ("4750", "Part time", "SN,WN", 17.50, 25.50, 2, None), + ("8253", "Full time", "WN,SN", 17.00, 25.00, 1, None), + ], + }, + { + "title": "General Merchandise Associate", + "area": "stores-and-clubs", + "category": "General Merchandise, Stocking, and Unloading", + "hashtag": "#storejobs", + "summary": "Unload, sort and stock general merchandise across the sales floor.", + "do": [ + "General Merchandise Associates at {banner} #{store} in {city}, {state} unload trailers, sort " + "freight to department, stock shelves and pull back the overhead so the floor stays full.", + "You will work modular resets, price changes and seasonal transitions alongside the department " + "team lead.", + ], + "bring": [ + "Unload and sort freight accurately to department.", + "Stock, zone and face assigned departments to company standard.", + "Execute modular resets and seasonal transitions on schedule.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2050", "Part time", "WN,SN", 17.50, 28.00, 3, None), + ("3387", "Part time", "WD,SD", 15.00, 25.00, 2, None), + ("1179", "Full time", "WE,SE,WN", 15.00, 25.00, 4, None), + ("954", "Part time", "WD,WE", 15.00, 24.00, 2, None), + ], + }, + { + "title": "Stocking Associate", + "area": "stores-and-clubs", + "category": "General Merchandise, Stocking, and Unloading", + "hashtag": "#storejobs", + "summary": "Work the overnight stocking team, filling the store before the doors open.", + "do": [ + "Stocking Associates at {banner} #{store} in {city}, {state} work the truck overnight: unload, " + "sort to aisle, stock, break down cardboard and zone the floor so the store looks new at open.", + "The pace is set by the truck. You will move steadily for the whole shift with a small team and " + "a clear finish line.", + ], + "bring": [ + "Stock assigned aisles completely and accurately before the store opens.", + "Break down and bale cardboard, keeping aisles clear and safe.", + "Use a manual pallet jack and rolltainers safely in tight aisles.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("1230", "Full time", "WN", 15.50, 24.50, 3, None), + ("2163", "Part time", "SN,WN", 16.50, 26.50, 2, None), + ], + }, + { + "title": "Merchandising and Stocking Associate", + "area": "stores-and-clubs", + "category": "General Merchandise, Stocking, and Unloading", + "hashtag": "#samsclubjobs", + "summary": "Build club pallets and keep the sales floor merchandised to plan.", + "do": [ + "At {banner} #{store} in {city}, {state} you stock bulk club pallets, build feature displays at " + "the action alley, and keep signage and pricing accurate across your zone.", + "You will use an electric pallet jack and order picker after certification, and you will work " + "with the merchandising lead on weekly display changes.", + ], + "bring": [ + "Stock club pallets and build feature displays to the merchandising plan.", + "Verify signage and pricing against the weekly plan every shift.", + "Operate an electric pallet jack and order picker after certification.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("4750", "Part time", "SN,WN", 17.00, 19.50, 3, None), + ("6318", "Part time", "SN,SD", 21.00, 24.50, 2, None), + ("6636", "Full time", "SN,WN", 18.00, 19.75, 4, None), + ("8253", "Part time", "SN,WE", 17.50, 19.25, 2, None), + ("6216", "Part time", "WN,WD", 18.50, 19.90, 3, None), + ("8763", "Part time", "SN,SE", 16.50, 18.75, 1, None), + ], + }, + { + "title": "Online Order Filling Team Associate", + "area": "stores-and-clubs", + "category": "Digital Pickup and Delivery", + "hashtag": "#storejobs", + "summary": "Shop, stage and hand off customer pickup and delivery orders.", + "do": [ + "Online Order Filling Team Associates at {banner} #{store} in {city}, {state} shop customer " + "orders from the sales floor with a cart and a handheld, choose the freshest substitutions when " + "an item is out, and stage completed orders in the pickup coolers.", + "You will also load orders into customer vehicles at the pickup canopy and hand off to delivery " + "drivers on schedule.", + ], + "bring": [ + "Pick customer orders accurately against the handheld pick list.", + "Choose quality substitutions and communicate them to the customer.", + "Stage orders at the correct temperature and load them at the pickup canopy.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("1399", "Part time", "WD,WE,SD", 14.50, 27.50, 3, None), + ("144", "Full time", "WD,SD", 15.50, 26.00, 2, None), + ("5991", "Part time", "WE,SE", 17.50, 28.00, 4, None), + ("5260", "Full time", "WD,WE,SE", 15.00, 28.00, 2, None), + ("5388", "Part time", "WE,SE", 16.00, 27.00, 2, None), + ], + }, + { + "title": "Online Order Filling Team Supervisor", + "area": "stores-and-clubs", + "category": "Digital Pickup and Delivery", + "hashtag": "#storejobs", + "summary": "Lead the pickup and delivery team through the day's order volume.", + "do": [ + "The Online Order Filling Team Supervisor at {banner} #{store} in {city}, {state} runs the " + "digital team for the shift: assigns pickers, watches the order clock, and steps in wherever " + "the queue is tightest.", + "You will coach on pick quality and substitution decisions, handle escalated customer issues at " + "the canopy, and report on-time performance to the store lead each day.", + ], + "bring": [ + "Assign and balance pick work across the team through peak windows.", + "Coach associates on pick accuracy, substitutions and customer handoff.", + "Resolve escalated pickup and delivery issues at the canopy.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2073", "Full time", "WD,SD", 20.00, 33.00, 2, None), + ("1179", "Full time", "WD,WE", 19.50, 32.00, 1, None), + ("5439", "Part time", "WE,SE", 19.00, 31.50, 2, None), + ], + }, + { + "title": "Cafe Associate", + "area": "stores-and-clubs", + "category": "Cafe", + "hashtag": "#samsclubjobs", + "summary": "Run the club cafe: prep, grill, serve and keep the counter to food safety standard.", + "do": [ + "Cafe Associates at {banner} #{store} in {city}, {state} take orders, prep and cook to the " + "posted recipe cards, and keep the counter, the drink station and the seating area clean " + "through the rush.", + "You will run opening or closing food safety checklists and log temperatures on every batch.", + ], + "bring": [ + "Prepare food to recipe and hold it at safe temperatures.", + "Complete opening, mid-shift and closing food safety logs.", + "Keep the counter, equipment and seating area clean and stocked.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6608", "Part time", "WD,WE", 17.00, 24.00, 2, None), + ("8259", "Part time", "WD,SD", 16.00, 23.00, 1, None), + ("6318", "Part time", "SD,SE,FX", 15.50, 22.50, 3, None), + ("6636", "Part time", "WD,SD", 16.00, 23.00, 2, None), + ], + }, + { + "title": "Team Lead", + "area": "stores-and-clubs", + "category": "Retail Management", + "hashtag": "#storejobs", + "summary": "Lead a department team, own its standards and develop the associates on it.", + "do": [ + "Team Leads at {banner} #{store} in {city}, {state} run a department end to end: staffing the " + "shift, setting priorities at the huddle, working the floor alongside the team and owning the " + "department's sales and in-stock results.", + "You will coach associates day to day, handle customer escalations, and partner with the coach " + "on scheduling and development plans.", + ], + "bring": [ + "Plan and assign daily work for a department team.", + "Coach associates on standards and follow through on development plans.", + "Own department in-stock, shrink and customer experience results.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("144", "Full time", "WD,WE", 19.00, 32.00, 1, None), + ("2073", "Full time", "WD,WE", 20.00, 33.00, 1, None), + ("4137", "Full time", "WD,SD", 21.00, 34.00, 2, None), + ("2610", "Full time", "WE,SE", 18.00, 30.00, 1, None), + ], + }, + { + "title": "Coach", + "area": "stores-and-clubs", + "category": "Retail Management", + "hashtag": "#storejobs", + "summary": "Lead several departments and the team leads who run them.", + "do": [ + "Coaches at {banner} #{store} in {city}, {state} lead a group of departments and the team leads " + "inside them, owning results for sales, availability, shrink and associate engagement across " + "that area of the building.", + "You will spend the day on the floor, remove barriers for your leads, and hold the standard " + "when the store is busiest.", + ], + "bring": [ + "Lead team leads and associates across multiple departments.", + "Own area results for sales, in-stock, shrink and engagement.", + "Build talent through structured coaching and succession planning.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("1230", "Full time", "WD,SD", 23.00, 38.00, 1, None), + ("2163", "Full time", "WE,SE", 24.00, 39.00, 1, None), + ], + }, + { + "title": "Fuel Station Associate", + "area": "stores-and-clubs", + "category": "Fuel Station", + "hashtag": "#samsclubjobs", + "summary": "Run the club fuel station: assist members, check equipment and keep the site compliant.", + "do": [ + "Fuel Station Associates at {banner} #{store} in {city}, {state} greet members at the pumps, " + "help with payment issues, complete daily equipment and environmental checks, and keep the " + "island clean and stocked.", + "You will work outdoors in all weather and follow strict fuel handling and spill response " + "procedures.", + ], + "bring": [ + "Complete daily fuel equipment, tank and environmental compliance checks.", + "Assist members at the pump and resolve payment issues.", + "Follow fuel handling, spill response and emergency shutdown procedures.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6636", "Part time", "WD,SD,FX", 16.50, 23.50, 2, None), + ("8253", "Part time", "WE,SE", 15.50, 22.50, 1, None), + ("6216", "Part time", "SN,WN", 17.00, 24.00, 2, None), + ("4750", "Full time", "WD,WN", 16.00, 23.00, 1, None), + ], + }, + { + "title": "Auto Care Center Technician", + "area": "stores-and-clubs", + "category": "Auto Care Center", + "hashtag": "#storejobs", + "summary": "Perform tire, battery and light maintenance service in the Auto Care Center.", + "do": [ + "Auto Care Center Technicians at {banner} #{store} in {city}, {state} mount and balance tires, " + "install batteries, change oil and complete light maintenance services while the customer " + "shops.", + "You will inspect vehicles honestly, document every service performed, and keep the bay and " + "equipment to safety standard.", + ], + "bring": [ + "Mount, balance and repair tires and install batteries safely.", + "Complete oil changes and light maintenance to manufacturer specification.", + "Document every inspection and service accurately in the shop system.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("954", "Full time", "WD,SD", 17.00, 30.00, 3, None), + ("1230", "Full time", "WD,WE", 17.00, 30.00, 5, None), + ("471", "Part time", "WE,SE", 16.50, 29.00, 2, None), + ("1179", "Full time", "WD,SD", 17.50, 30.50, 1, None), + ], + }, + { + "title": "Tire & Battery Technician", + "area": "stores-and-clubs", + "category": "Auto Services", + "hashtag": "#samsclubjobs", + "summary": "Service member vehicles in the club tire and battery center.", + "do": [ + "Tire & Battery Technicians at {banner} #{store} in {city}, {state} install and rotate tires, " + "test and replace batteries, and complete the free member services the club is known for.", + "You will work the service write-up desk as well as the bay, so clear explanations matter as " + "much as clean work.", + ], + "bring": [ + "Install, rotate and repair tires to torque and safety specification.", + "Test and replace batteries and charging system components.", + "Write up member services clearly and set accurate expectations on timing.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6608", "Full time", "WD,WE", 19.00, 27.00, 2, None), + ("4750", "Part time", "SD,SE", 17.50, 25.50, 1, None), + ("6216", "Full time", "WD,SD", 18.50, 26.50, 2, None), + ("8259", "Part time", "WE,SN", 17.00, 25.00, 1, None), + ], + }, + { + "title": "Maintenance Technician", + "area": "stores-and-clubs", + "category": "Maintenance", + "hashtag": "#storejobs", + "summary": "Keep store equipment, refrigeration and building systems running.", + "do": [ + "Maintenance Technicians at {banner} #{store} in {city}, {state} respond to equipment calls " + "across the building: refrigeration alarms, doors, carts, lighting, HVAC and the compactor.", + "You will complete scheduled preventive maintenance, escalate refrigerant work to the " + "certified contractor, and close every work order with what you actually did.", + ], + "bring": [ + "Diagnose and repair store equipment, lighting and building systems.", + "Complete scheduled preventive maintenance on time.", + "Follow lockout/tagout and electrical safety procedures without exception.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2073", "Full time", "WN,SN,FX", 20.00, 30.00, 1, None), + ("1399", "Part time", "WE,SE,FX", 18.00, 28.00, 2, None), + ("5439", "Full time", "WD,WE,FX", 19.00, 29.00, 1, None), + ("3512", "Full time", "WD,SD", 17.00, 26.00, 2, None), + ], + }, + { + "title": "Asset Protection Associate", + "area": "stores-and-clubs", + "category": "Security and Asset Protection", + "hashtag": "#storejobs", + "summary": "Reduce shrink and keep associates and customers safe inside the store.", + "do": [ + "Asset Protection Associates at {banner} #{store} in {city}, {state} work the floor and the " + "camera room, deter theft, respond to alarms, and partner with store leadership on safety " + "walks and incident follow-up.", + "You will document every incident to policy and work with local law enforcement when the " + "asset protection manager asks you to.", + ], + "bring": [ + "Deter and document theft following company approach and apprehension policy.", + "Monitor camera and EAS systems and respond to alarms.", + "Complete safety walks and incident reports accurately.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("5991", "Full time", "WD,WE,SD", 17.00, 30.00, 2, None), + ("3387", "Part time", "SE,SN", 16.00, 28.00, 1, None), + ("5382", "Full time", "WD,SD", 19.00, 32.00, 1, None), + ("5388", "Full time", "WD,SD", 18.00, 30.00, 2, None), + ], + }, + { + "title": "Asset Protection Customer Specialist", + "area": "stores-and-clubs", + "category": "Security and Asset Protection", + "hashtag": "#storejobs", + "summary": "Greet at the entrance, verify receipts and keep the front of the store secure.", + "do": [ + "Asset Protection Customer Specialists at {banner} #{store} in {city}, {state} work the " + "entrance: greeting every customer, verifying receipts at the door, and watching the front end " + "for problems before they grow.", + "You will support the asset protection team with documentation and keep the entry area clean, " + "carted and welcoming.", + ], + "bring": [ + "Greet customers at the entrance and verify receipts at the exit.", + "Watch front-end activity and escalate concerns to asset protection.", + "Keep the entry area stocked with carts and free of hazards.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2503", "Full time", "WD,WE", 16.00, 26.00, 1, None), + ("5260", "Part time", "WE,SE", 17.00, 28.00, 2, None), + ], + }, + # ------------------------------- Healthcare ---------------------------- + { + "title": "Pharmacy Technician", + "area": "healthcare", + "category": "Pharmacy Services", + "hashtag": "#healthcarejobs", + "summary": "Support the pharmacist with intake, data entry, filling and patient pickup.", + "do": [ + "Pharmacy Technicians at {banner} #{store} in {city}, {state} take in prescriptions, enter and " + "verify patient and insurance information, count and label under the pharmacist's supervision, " + "and hand off at the pickup window.", + "You will work third-party rejections, call prescribers for clarifications, and keep the " + "workflow moving so patients are not waiting on paperwork.", + ], + "bring": [ + "Enter prescription and insurance information accurately into the pharmacy system.", + "Fill and label prescriptions under the direct supervision of the pharmacist.", + "Resolve third-party rejections and coordinate with prescriber offices.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("5260", "Full time", "WD,SD", 18.00, 30.00, 2, None), + ("4137", "Part time", "WE,SE", 19.50, 32.00, 1, None), + ("2110", "Full time", "WD,WE", 18.50, 30.50, 1, None), + ("2050", "Part time", "SD,SE", 20.00, 33.00, 2, None), + ], + }, + { + "title": "Certified Pharmacy Technician", + "area": "healthcare", + "category": "Pharmacy Services", + "hashtag": "#healthcarejobs", + "summary": "Work at the top of your certification supporting immunizations and clinical services.", + "do": [ + "Certified Pharmacy Technicians at {banner} #{store} in {city}, {state} do everything a " + "technician does, plus the work that certification unlocks: immunization support, medication " + "therapy outreach and inventory ownership for controlled substances.", + "You will mentor uncertified technicians on workflow and accuracy, and cover the pharmacist's " + "administrative queue during clinical blocks.", + ], + "bring": [ + "Hold and maintain a current state pharmacy technician certification.", + "Support immunization clinics and medication therapy outreach.", + "Own perpetual inventory counts for controlled substances.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("3520", "Full time", "WD,WE", 21.00, 34.00, 1, None), + ("1236", "Part time", "WE,SD", 19.00, 31.00, 2, None), + ], + }, + { + "title": "Optician", + "area": "healthcare", + "category": "Optical Services", + "hashtag": "#healthcarejobs", + "summary": "Fit, adjust and dispense eyewear in the Vision Center.", + "do": [ + "Opticians at {banner} #{store} in {city}, {state} interpret prescriptions, take measurements, " + "recommend lens options honestly, and fit and adjust finished eyewear so it is comfortable on " + "day one.", + "You will also run the lab bench work the store handles in house, manage the frame board, and " + "coordinate with the independent optometrist's office next door.", + ], + "bring": [ + "Interpret ophthalmic prescriptions and take accurate fitting measurements.", + "Recommend frames and lens treatments suited to the prescription and budget.", + "Adjust, repair and dispense finished eyewear to specification.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("5991", "Full time", "WD,WE,SD", 22.00, 35.00, 2, None), + ("2073", "Full time", "WD,SD", 21.50, 34.50, 1, None), + ("5382", "Part time", "WE,SE", 23.00, 36.00, 1, None), + ("1236", "Full time", "WD,WE", 21.00, 34.00, 1, None), + ], + }, + { + "title": "Vision Center Associate", + "area": "healthcare", + "category": "Optical Services", + "hashtag": "#healthcarejobs", + "summary": "Greet vision center customers, schedule exams and support the optician.", + "do": [ + "Vision Center Associates at {banner} #{store} in {city}, {state} welcome customers, schedule " + "exams with the on-site optometrist, verify vision benefits and support the optician with " + "dispensing and repairs.", + "You will keep the frame board merchandised and the exam schedule full without overbooking.", + ], + "bring": [ + "Schedule exams and verify vision insurance benefits.", + "Support dispensing, adjustments and simple frame repairs.", + "Keep the frame board merchandised, priced and clean.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("471", "Part time", "WD,SD", 16.00, 26.00, 2, None), + ("5439", "Part time", "WE,SE", 17.00, 27.00, 1, None), + ], + }, + { + "title": "Health & Wellness Operations Associate", + "area": "healthcare", + "category": "Health and Wellness Operations", + "hashtag": "#healthcarejobs", + "summary": "Keep the health and wellness area stocked, compliant and ready for patients.", + "do": [ + "Health & Wellness Operations Associates at {banner} #{store} in {city}, {state} own the " + "operational side of the department: over-the-counter stocking, expiration audits, compliance " + "logs and the patient waiting area.", + "You will support screening events, keep the private consultation room ready, and route " + "patient questions to the pharmacist correctly.", + ], + "bring": [ + "Complete over-the-counter stocking and expiration audits on schedule.", + "Maintain compliance logs and the private consultation area.", + "Support screening and immunization events with setup and intake.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("2050", "Full time", "WD,WE", 18.00, 29.00, 1, None), + ("3387", "Part time", "SD,SE", 17.00, 28.00, 2, None), + ("3512", "Part time", "WD,SD", 17.50, 28.50, 1, None), + ("1399", "Part time", "WE,SN", 18.50, 29.50, 2, None), + ], + }, + { + "title": "Certified Medical Assistant", + "area": "healthcare", + "category": "Clinical Care", + "hashtag": "#healthcarejobs", + "summary": "Room patients, take vitals and support the clinician in a community care setting.", + "do": [ + "Certified Medical Assistants supporting {banner} #{store} in {city}, {state} greet and room " + "patients, take vitals and history, prepare the room, and assist the clinician during the " + "visit.", + "You will document in the electronic health record, handle specimen collection and labelling, " + "and close out visit instructions with the patient before they leave.", + ], + "bring": [ + "Hold a current medical assistant certification and BLS card.", + "Take and document vitals, history and medication reconciliation accurately.", + "Collect and label specimens following chain-of-custody procedures.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("1236", "Full time", "WD,WE", 19.00, 30.00, 1, None), + ("3520", "Part time", "WD,SD", 22.00, 34.00, 2, None), + ("2610", "Part time", "WE,SE", 19.50, 30.50, 1, None), + ("954", "Part time", "WD,SD", 18.00, 29.00, 2, None), + ], + }, + # ------------------------------- Students (hourly) --------------------- + { + "title": "Retail Operations Intern", + "area": "students", + "category": "Internship", + "hashtag": "#studentjobs", + "summary": "A paid store internship rotating through front end, digital and merchandising.", + "do": [ + "Retail Operations Interns at {banner} #{store} in {city}, {state} spend the term rotating " + "through the front end, the digital pickup team and a merchandising department, with a store " + "leader as your mentor.", + "You will finish the program by presenting one operational improvement you scoped, tested and " + "measured inside the building.", + ], + "bring": [ + "Currently enrolled in an associate or bachelor's degree program.", + "Availability for a full internship term including some weekend coverage.", + "Willingness to work the floor in every rotation, not just observe.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("144", "Intern", "WD,FX", 16.00, 20.00, 1, None), + ("2073", "Intern", "WD,FX", 16.50, 20.50, 1, None), + ("4137", "Intern", "WE,FX", 17.00, 21.00, 1, None), + ("2503", "Intern", "WD,SD", 15.00, 19.00, 1, None), + ], + }, + { + "title": "Club Operations Intern", + "area": "students", + "category": "Internship", + "hashtag": "#studentjobs", + "summary": "A paid club internship focused on membership growth and fresh operations.", + "do": [ + "Club Operations Interns at {banner} #{store} in {city}, {state} work with the club manager on " + "membership growth, fresh area operations and the weekly merchandising plan.", + "You will own one measurable project for the term and present the results to the club " + "leadership team.", + ], + "bring": [ + "Currently enrolled in an associate or bachelor's degree program.", + "Interest in retail operations, membership models or fresh category management.", + "Availability for a full internship term including some weekend coverage.", + "Complies with company policies, procedures, and standards of ethics and integrity.", + ], + "placements": [ + ("6608", "Intern", "WD,FX", 18.00, 22.00, 1, None), + ("6318", "Intern", "WE,FX", 16.50, 20.50, 1, None), + ], + }, +] + +# --------------------------------------------------------------------------- # +# Salaried title families. +# +# placement tuple: (store_number, employment_type, min_pay, max_pay, +# worker_type, qual_slots, extras) +# `qual_slots` = (degree_field, option1_years, option2_years, preferred_slot) +# `extras` is an optional dict: {"job_id": ...} +# Minimum-qualification text is built from the family's template with those +# slots, so every posting's Option 1 / Option 2 text is unique. +# --------------------------------------------------------------------------- # +SALARIED_FAMILIES = [ + { + "title": "Staff, Software Engineer - Backend / ML", + "area": "technology", + "category": "Software Engineering and Architecture", + "shifts": "WD,SD", + "summary": "Set the technical direction for backend microservices and ML-serving " + "infrastructure at retail scale.", + "do": [ + "As a Staff Software Engineer at {location_name} in {city}, {state}, you'll be a technical " + "leader who defines the direction for and evolves the backend microservices, data pipelines, " + "and ML-serving infrastructure that power search at massive scale. You'll lead a team of six " + "to ten engineers, set the technical vision for critical systems, and drive the quality bar " + "across the team.", + "We're in an active phase of platform modernization - redesigning and refactoring core " + "systems. If you want to build, not just maintain, this is the right time to join.", + ], + "about_team": "The eCommerce Search engineering team owns the end-to-end technology stack that " + "powers product search and discovery across Walmart's global eCommerce channels, " + "backed by microservices, large-scale data and feature pipelines, search engines, " + "and ML model serving infrastructure.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "software engineering or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in software engineering or related area.", + "preferred": "Master's degree in {degree_field} and {yp} years' experience in software " + "engineering or related area. We value candidates with a background in creating " + "inclusive digital experiences and knowledge of Web Content Accessibility " + "Guidelines (WCAG) 2.2 AA standards.", + "placements": [ + ("11807", "Full time", 143000, 286000, "Regular/Permanent", + ("computer science, computer engineering, computer information systems, software engineering, or related area", 4, 6, 2), + {"job_id": "R-2463275"}), + ("10101", "Full time", 132000, 264000, "Regular/Permanent", + ("computer science, computer engineering, or related area", 5, 8, 3), None), + ], + }, + { + "title": "Senior Software Engineer", + "area": "technology", + "category": "Software Engineering and Architecture", + "shifts": "WD", + "summary": "Design, build and operate the services behind checkout, fulfillment and search.", + "do": [ + "Senior Software Engineers at {location_name} in {city}, {state} own services end to end: " + "design, implementation, deployment and on-call. You will partner with product and data " + "science to turn ambiguous problems into systems that hold up at Walmart's traffic.", + "You will review designs and code across the team, mentor engineers earlier in their careers, " + "and keep an eye on cost, latency and reliability as much as on features.", + ], + "about_team": "This team builds and runs the platform services that thousands of engineers and " + "millions of customers depend on every day.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "software engineering or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in software engineering or related area.", + "preferred": "Master's degree in {degree_field} and {yp} years' experience building distributed " + "systems in production.", + "placements": [ + ("11003", "Full time", 110000, 220000, "Regular/Permanent", + ("computer science, computer information systems, or related area", 3, 5, 1), None), + ("11807", "Full time", 117000, 234000, "Regular/Permanent", + ("computer engineering, software engineering, or related area", 4, 7, 2), None), + ("10101", "Full time", 96000, 192000, "Regular/Permanent", + ("information systems, computer science, or related area", 2, 4, 1), None), + ], + }, + { + "title": "Software Engineer III", + "area": "technology", + "category": "Software Engineering and Architecture", + "shifts": "WD", + "summary": "Build features across the smart TV platform and its content services.", + "do": [ + "Software Engineers at {location_name} in {city}, {state} build and ship features across the " + "platform, from the on-device experience to the services behind it.", + "You will write production code every week, take part in design reviews, and work with QA and " + "product on release readiness.", + ], + "about_team": "The platform engineering group builds the software that runs on millions of " + "connected devices in customers' living rooms.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "software engineering or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in software engineering or related area.", + "preferred": "Experience with embedded platforms and {yp} years' experience shipping consumer " + "software at scale.", + "placements": [ + ("12200", "Full time", 90000, 180000, "Regular/Permanent", + ("computer science or related area", 2, 4, 3), None), + ], + }, + { + "title": "Senior Manager, Product Management", + "area": "technology", + "category": "Product Management", + "shifts": "WD", + "summary": "Own a product area end to end, from strategy through launch and iteration.", + "do": [ + "Senior Managers of Product Management at {location_name} in {city}, {state} own a product " + "area: the strategy, the roadmap, the trade-offs and the results.", + "You will work daily with engineering, design and data science, and you will be the person " + "who says no often enough that the yes means something.", + ], + "about_team": "Product management at Walmart sits close to the customer and close to the code.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "product management or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in product management or related area.", + "preferred": "Master's degree in business administration and {yp} years' experience leading " + "product teams.", + "placements": [ + ("10101", "Full time", 110000, 220000, "Regular/Permanent", + ("business, analytics, engineering, or related area", 5, 7, 2), None), + ("11807", "Full time", 132000, 264000, "Regular/Permanent", + ("computer science, business, or related area", 6, 9, 3), None), + ("11003", "Full time", 90000, 180000, "Regular/Permanent", + ("marketing, business, or related area", 4, 6, 1), None), + ], + }, + { + "title": "Director, Product Management", + "area": "technology", + "category": "Product Management", + "shifts": "WD,SD", + "summary": "Lead a portfolio of product areas and the managers who run them.", + "do": [ + "Directors of Product Management at {location_name} in {city}, {state} set direction for a " + "portfolio, hire and develop product managers, and represent the portfolio in company-level " + "planning.", + "You will spend your time on strategy, talent and unblocking - not on writing every " + "requirement yourself.", + ], + "about_team": "This portfolio spans several teams working on connected customer experiences.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "product management or related area, including {yp} years of people leadership.", + "min_qual_option2": "Option 2: {y2} years' experience in product management or related area, " + "including {yp} years of people leadership.", + "preferred": "Master's degree in business administration and experience owning a profit and loss " + "statement for {yp} years or more.", + "placements": [ + ("10101", "Full time", 130000, 260000, "Regular/Permanent", + ("business, engineering, or related area", 8, 11, 4), None), + ], + }, + { + "title": "Senior Data Scientist", + "area": "technology", + "category": "Data Science and Analytics", + "shifts": "WD", + "summary": "Build models that change what customers see and what the business decides.", + "do": [ + "Senior Data Scientists at {location_name} in {city}, {state} frame the problem, build the " + "model, ship it behind an experiment, and tell the story of what it did.", + "You will work in Python and SQL against very large datasets, and you will be expected to " + "defend your methodology to people who will use the results.", + ], + "about_team": "Data science sits inside the product teams here, not in a separate lab.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "an analytics or data science role.", + "min_qual_option2": "Option 2: {y2} years' experience in an analytics or data science role.", + "preferred": "Master's or PhD in {degree_field} and {yp} years' experience deploying models to " + "production.", + "placements": [ + ("10101", "Full time", 108000, 216000, "Regular/Permanent", + ("statistics, economics, computer science, or related area", 4, 6, 2), None), + ("11807", "Full time", 130000, 260000, "Regular/Permanent", + ("machine learning, statistics, or related area", 5, 8, 3), None), + ("11003", "Full time", 110000, 190000, "Regular/Permanent", + ("applied mathematics, statistics, or related area", 3, 5, 1), None), + ("11500", "Full time", 100000, 175000, "Regular/Permanent", + ("operations research, statistics, or related area", 3, 6, 2), None), + ], + }, + { + "title": "Senior Manager, Information Security", + "area": "technology", + "category": "Information Security", + "shifts": "WD,SD", + "summary": "Lead a security function protecting customer and associate data at scale.", + "do": [ + "Senior Managers of Information Security at {location_name} in {city}, {state} lead a security " + "team, set the control standard for their domain, and partner with engineering on how those " + "controls actually get implemented.", + "You will own incident response readiness for your area and report risk posture to leadership " + "on a regular cadence.", + ], + "about_team": "Information security here is embedded with the teams it protects.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "information security or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in information security or related area.", + "preferred": "CISSP or equivalent certification and {yp} years' experience leading security teams.", + "placements": [ + ("10101", "Full time", 115000, 230000, "Regular/Permanent", + ("information technology, cybersecurity, or related area", 5, 8, 3), None), + ("11807", "Full time", 140000, 280000, "Regular/Permanent", + ("computer science, cybersecurity, or related area", 6, 9, 4), None), + ], + }, + { + "title": "Information Security Engineer III", + "area": "technology", + "category": "Information Security", + "shifts": "WD", + "summary": "Engineer and operate the detection and prevention controls that protect the platform.", + "do": [ + "Security Engineers at {location_name} in {city}, {state} build detections, tune controls, and " + "work incidents alongside the response team.", + "You will write code, not just configure tools, and you will be on a rotation.", + ], + "about_team": "This team keeps the platform defensible as it changes weekly.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "information security or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in information security or related area.", + "preferred": "Experience with cloud security tooling and {yp} years' experience in detection " + "engineering.", + "placements": [ + ("11003", "Full time", 80000, 160000, "Regular/Permanent", + ("cybersecurity, information systems, or related area", 2, 4, 1), None), + ("12200", "Full time", 95000, 170000, "Regular/Permanent", + ("computer engineering, cybersecurity, or related area", 3, 5, 2), None), + ], + }, + { + "title": "Senior UX Designer", + "area": "technology", + "category": "Creative Design and UX", + "shifts": "WD", + "summary": "Design flows that millions of people use without thinking about them.", + "do": [ + "Senior UX Designers at {location_name} in {city}, {state} own the experience for a product " + "area: research synthesis, flows, prototypes and the detailed specs engineering builds from.", + "You will test your work with real customers and change it when the test says so.", + ], + "about_team": "Design partners directly with product and engineering from the first week of a " + "project.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "user experience design or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in user experience design or related area.", + "preferred": "A portfolio showing shipped consumer work and {yp} years' experience with design " + "systems.", + "placements": [ + ("11807", "Full time", 120000, 240000, "Regular/Permanent", + ("design, human-computer interaction, or related area", 5, 7, 3), None), + ("11003", "Full time", 96000, 186000, "Regular/Permanent", + ("interaction design, visual design, or related area", 3, 6, 2), None), + ("12200", "Full time", 88000, 165000, "Regular/Permanent", + ("industrial design, human factors, or related area", 3, 5, 2), None), + ], + }, + { + "title": "Principal UX Researcher", + "area": "technology", + "category": "Creative Design and UX", + "shifts": "WD", + "summary": "Set the research agenda for a large product organization.", + "do": [ + "Principal UX Researchers at {location_name} in {city}, {state} decide what the organization " + "needs to learn next, design the studies that answer it, and make sure the answer changes " + "what gets built.", + "You will mentor researchers across teams and raise the methodological bar for everyone.", + ], + "about_team": "Research here reports into design and works across several product areas at once.", + "min_qual_option1": "Option 1: Master's degree in {degree_field} and {y1} years' experience in " + "user research or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in user research or related area.", + "preferred": "PhD in {degree_field} and {yp} years' experience leading mixed-methods research " + "programs.", + "placements": [ + ("11807", "Full time", 150000, 275000, "Regular/Permanent", + ("psychology, human-computer interaction, or related area", 7, 10, 4), None), + ], + }, + { + "title": "Senior Technical Program Manager", + "area": "technology", + "category": "Technical Program Management", + "shifts": "WD", + "summary": "Drive cross-team technical programs from commitment to launch.", + "do": [ + "Senior Technical Program Managers at {location_name} in {city}, {state} own the plan, the " + "risks and the communication for programs that span several engineering teams.", + "You will be technical enough to challenge an estimate and organized enough that nobody has " + "to ask you for a status.", + ], + "about_team": "Technical program management sits with engineering leadership here.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "technical program management or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in technical program management or related " + "area.", + "preferred": "Experience running programs across distributed teams for {yp} years or more.", + "placements": [ + ("11807", "Full time", 125000, 250000, "Regular/Permanent", + ("engineering, computer science, or related area", 5, 8, 3), None), + ("10101", "Full time", 105000, 210000, "Regular/Permanent", + ("information systems, engineering, or related area", 4, 7, 2), None), + ("12200", "Full time", 92000, 175000, "Regular/Permanent", + ("electrical engineering, computer science, or related area", 3, 6, 2), None), + ("11003", "Full time", 99000, 195000, "Regular/Permanent", + ("industrial engineering, business, or related area", 4, 6, 2), None), + ], + }, + { + "title": "IT Support Engineer", + "area": "technology", + "category": "Information Technology", + "shifts": "WD,SD", + "summary": "Keep the people who work here productive, from laptops to conference rooms.", + "do": [ + "IT Support Engineers at {location_name} in {city}, {state} handle escalated endpoint, " + "identity and collaboration issues for the associates on site.", + "You will automate the repeat offenders instead of fixing them one ticket at a time.", + ], + "about_team": "Workplace technology supports every associate in the building.", + "min_qual_option1": "Option 1: Associate's degree in {degree_field} and {y1} years' experience in " + "information technology support or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in information technology support or " + "related area.", + "preferred": "Scripting experience and {yp} years' experience with endpoint management tooling.", + "placements": [ + ("10101", "Full time", 70000, 140000, "Regular/Permanent", + ("information technology or related area", 2, 4, 2), None), + ("11500", "Full time", 68000, 136000, "Regular/Permanent", + ("computer information systems or related area", 2, 5, 1), None), + ("12200", "Full time", 72000, 144000, "Regular/Permanent", + ("network administration or related area", 3, 5, 2), None), + ("11109", "Full time", 66000, 132000, "Regular/Permanent", + ("information systems or related area", 1, 3, 1), None), + ], + }, + { + "title": "Senior Manager, Finance", + "area": "corporate", + "category": "Accounting and Finance", + "shifts": "WD", + "summary": "Lead financial planning and analysis for a business unit.", + "do": [ + "Senior Managers of Finance at {location_name} in {city}, {state} run the planning cycle for " + "their business unit, build the models leadership decides from, and lead a small team of " + "analysts.", + "You will be in the room when the trade-offs are made, and you will be expected to have a " + "point of view.", + ], + "about_team": "Finance partners are embedded with the businesses they support.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "accounting, finance or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in accounting, finance or related area.", + "preferred": "CPA or MBA and {yp} years' experience leading finance teams.", + "placements": [ + ("10101", "Full time", 100000, 200000, "Regular/Permanent", + ("accounting, finance, or related area", 5, 7, 3), None), + ("11500", "Full time", 90000, 180000, "Regular/Permanent", + ("finance, economics, or related area", 4, 6, 2), None), + ], + }, + { + "title": "Financial Analyst III", + "area": "corporate", + "category": "Accounting and Finance", + "shifts": "WD", + "summary": "Build the forecasts, variance analysis and business cases the team runs on.", + "do": [ + "Financial Analysts at {location_name} in {city}, {state} own a piece of the forecast, explain " + "variances to plan, and build the business cases that support investment decisions.", + "You will live in spreadsheets and the planning system, and you will present your work " + "directly to business leaders.", + ], + "about_team": "This team supports one of the largest cost centers in the company.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "financial analysis or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in financial analysis or related area.", + "preferred": "Advanced modelling skills and {yp} years' experience in a retail or supply chain " + "finance team.", + "placements": [ + ("10101", "Full time", 70000, 130000, "Regular/Permanent", + ("finance, accounting, or related area", 2, 4, 2), None), + ("11109", "Full time", 68000, 126000, "Regular/Permanent", + ("accounting, business, or related area", 2, 5, 1), None), + ("11500", "Full time", 72000, 134000, "Regular/Permanent", + ("economics, finance, or related area", 3, 5, 2), None), + ], + }, + { + "title": "Senior Manager, People Partner", + "area": "corporate", + "category": "Human Resources", + "shifts": "WD", + "summary": "Partner with business leaders on talent, org design and associate experience.", + "do": [ + "Senior Managers, People Partner at {location_name} in {city}, {state} advise leaders on " + "organisation design, talent planning and the hard conversations, and own the people plan for " + "their client group.", + "You will use data as well as judgement, and you will be the person associates trust to be " + "straight with them.", + ], + "about_team": "People partners support the business teams in the building directly.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "human resources or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in human resources or related area.", + "preferred": "SHRM-SCP certification and {yp} years' experience supporting technology " + "organisations.", + "placements": [ + ("10101", "Full time", 96000, 186000, "Regular/Permanent", + ("human resources, business, or related area", 5, 7, 3), None), + ("11003", "Full time", 100000, 195000, "Regular/Permanent", + ("industrial relations, psychology, or related area", 4, 6, 2), None), + ], + }, + { + "title": "HR Business Partner", + "area": "corporate", + "category": "Human Resources", + "shifts": "WD", + "summary": "Support a client group across hiring, performance and associate relations.", + "do": [ + "HR Business Partners at {location_name} in {city}, {state} run the people cycle for their " + "client group: hiring plans, performance calibration, development and associate relations " + "cases.", + "You will coach managers who are new to leading people and hold the line on policy when it " + "matters.", + ], + "about_team": "This team supports several hundred associates across the site.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "human resources or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in human resources or related area.", + "preferred": "Experience with associate relations investigations for {yp} years or more.", + "placements": [ + ("11500", "Full time", 80000, 150000, "Regular/Permanent", + ("human resources or related area", 3, 5, 2), None), + ("11109", "Full time", 78000, 146000, "Regular/Permanent", + ("business administration or related area", 2, 4, 1), None), + ], + }, + { + "title": "Manager, Marketing", + "area": "corporate", + "category": "Marketing and Advertising", + "shifts": "WD", + "summary": "Own campaign strategy and execution for a product line.", + "do": [ + "Marketing Managers at {location_name} in {city}, {state} own the plan for a product line: " + "positioning, campaign calendar, agency briefs and the results readout.", + "You will work with creative, media and analytics, and you will be accountable for what the " + "spend returned.", + ], + "about_team": "Marketing here works close to the product teams and the sales calendar.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "marketing or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in marketing or related area.", + "preferred": "Experience running integrated consumer campaigns for {yp} years or more.", + "placements": [ + ("12200", "Full time", 85000, 160000, "Regular/Permanent", + ("marketing, communications, or related area", 3, 5, 2), None), + ("10101", "Full time", 90000, 170000, "Regular/Permanent", + ("marketing, business, or related area", 4, 6, 3), None), + ], + }, + { + "title": "Senior Manager, Brand Marketing", + "area": "corporate", + "category": "Marketing and Advertising", + "shifts": "WD", + "summary": "Lead brand strategy and the campaigns that carry it.", + "do": [ + "Senior Managers of Brand Marketing at {location_name} in {city}, {state} own how the brand " + "shows up: the platform, the creative standard and the campaigns that put it in front of " + "customers.", + "You will lead a small team and manage agency partners against a real budget.", + ], + "about_team": "Brand marketing sets the standard the rest of marketing works to.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "brand or consumer marketing.", + "min_qual_option2": "Option 2: {y2} years' experience in brand or consumer marketing.", + "preferred": "Master's degree in business administration and {yp} years' experience managing " + "agency relationships.", + "placements": [ + ("10101", "Full time", 110000, 210000, "Regular/Permanent", + ("marketing, advertising, or related area", 6, 8, 3), None), + ("11500", "Full time", 105000, 200000, "Regular/Permanent", + ("communications, marketing, or related area", 5, 7, 2), None), + ], + }, + { + "title": "Marketing Specialist III", + "area": "corporate", + "category": "Marketing and Advertising", + "shifts": "WD", + "summary": "Execute member marketing programs and report on what they returned.", + "do": [ + "Marketing Specialists at {location_name} in {city}, {state} execute the member marketing " + "calendar: briefs, asset trafficking, channel setup and post-campaign reporting.", + "You will keep several campaigns moving at once and be the person who notices the detail " + "everyone else missed.", + ], + "about_team": "Member marketing owns how the club talks to its members between visits.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "marketing or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in marketing or related area.", + "preferred": "Experience with customer relationship management platforms for {yp} years or more.", + "placements": [ + ("11109", "Full time", 65000, 120000, "Regular/Permanent", + ("marketing or related area", 2, 4, 1), None), + ], + }, + { + "title": "Senior Buyer", + "area": "corporate", + "category": "Merchandising", + "shifts": "WD", + "summary": "Own assortment, cost and supplier relationships for a category.", + "do": [ + "Senior Buyers at {location_name} in {city}, {state} own a category: what we carry, what we " + "pay for it, and how it performs on the floor.", + "You will negotiate with suppliers, build the assortment plan by season, and answer for the " + "category's sales and margin every month.", + ], + "about_team": "Merchandising decides what ends up on the shelf and at what price.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "merchandising, buying or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in merchandising, buying or related area.", + "preferred": "Experience negotiating national supplier agreements for {yp} years or more.", + "placements": [ + ("10101", "Full time", 95000, 185000, "Regular/Permanent", + ("business, merchandising, or related area", 5, 7, 3), None), + ("11109", "Full time", 92000, 178000, "Regular/Permanent", + ("supply chain, business, or related area", 4, 6, 2), None), + ], + }, + { + "title": "Merchandising Manager", + "area": "corporate", + "category": "Merchandising", + "shifts": "WD", + "summary": "Turn category strategy into the plan the stores and clubs actually execute.", + "do": [ + "Merchandising Managers at {location_name} in {city}, {state} translate category strategy into " + "modulars, promotions and in-club execution plans.", + "You will work with buyers, replenishment and field leadership to make sure the plan survives " + "contact with the sales floor.", + ], + "about_team": "This team bridges the buying office and the buildings.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "merchandising or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in merchandising or related area.", + "preferred": "Field retail experience and {yp} years' experience with space planning tools.", + "placements": [ + ("10101", "Full time", 88000, 170000, "Regular/Permanent", + ("merchandising, business, or related area", 4, 6, 2), None), + ("11109", "Full time", 85000, 165000, "Regular/Permanent", + ("retail management, business, or related area", 3, 5, 2), None), + ], + }, + { + "title": "Replenishment Manager", + "area": "corporate", + "category": "Merchandising", + "shifts": "WD", + "summary": "Own in-stock and inventory turns for a category across the network.", + "do": [ + "Replenishment Managers at {location_name} in {city}, {state} own in-stock, forecast accuracy " + "and inventory turns for their categories across the whole network.", + "You will tune forecasting parameters, work supplier lead times, and be the first call when a " + "category goes out of stock in a region.", + ], + "about_team": "Replenishment keeps thousands of buildings full without drowning them in " + "inventory.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "replenishment, supply chain or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in replenishment, supply chain or related " + "area.", + "preferred": "Experience with demand forecasting systems for {yp} years or more.", + "placements": [ + ("10101", "Full time", 84000, 162000, "Regular/Permanent", + ("supply chain, industrial engineering, or related area", 4, 6, 2), None), + ], + }, + { + "title": "Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery)", + "area": "corporate", + "category": "Business Operations", + "shifts": "WD,SD", + "summary": "Own the operations behind driver matching and arrival accuracy for last mile delivery.", + "do": [ + "Senior Managers on Last Mile Delivery at {location_name} in {city}, {state} own the " + "operational levers behind delivery search, driver arrival and order matching: the policies, " + "the thresholds and the escalation paths that keep deliveries on time.", + "You will work with product and data science on where the model ends and operations begins, " + "and you will own the metric either way.", + ], + "about_team": "Last Mile Delivery moves millions of orders from the building to the doorstep.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "operations management or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in operations management or related area.", + "preferred": "Master's degree in {degree_field} and {yp} years' experience in last mile or " + "transportation operations.", + "placements": [ + ("10101", "Full time", 110000, 220000, "Regular/Permanent", + ("supply chain management, operations, or related area", 5, 7, 3), + {"job_id": "R-2451180"}), + ("11003", "Full time", 117000, 234000, "Regular/Permanent", + ("industrial engineering, logistics, or related area", 3, 5, 2), None), + ], + }, + { + "title": "Manager, Supply Chain Operations", + "area": "corporate", + "category": "Business Operations", + "shifts": "WD", + "summary": "Run network planning and continuous improvement for a supply chain region.", + "do": [ + "Managers of Supply Chain Operations at {location_name} in {city}, {state} own network " + "planning, cost-to-serve analysis and continuous improvement projects for their region.", + "You will spend time in the buildings, not only in the model, and you will bring changes back " + "that the operators can actually run.", + ], + "about_team": "Supply chain operations connects the network plan to what happens on the dock.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "supply chain or operations.", + "min_qual_option2": "Option 2: {y2} years' experience in supply chain or operations.", + "preferred": "Lean or Six Sigma certification and {yp} years' experience leading improvement " + "projects.", + "placements": [ + ("11500", "Full time", 82000, 158000, "Regular/Permanent", + ("supply chain management or related area", 3, 5, 2), None), + ("10101", "Full time", 86000, 166000, "Regular/Permanent", + ("industrial engineering, logistics, or related area", 4, 6, 2), None), + ], + }, + { + "title": "Business Operations Analyst III", + "area": "corporate", + "category": "Business Operations", + "shifts": "WD", + "summary": "Turn operational data into the decisions the business runs on.", + "do": [ + "Business Operations Analysts at {location_name} in {city}, {state} build the reporting, run " + "the analysis and write the recommendation that leadership acts on.", + "You will be trusted with the numbers, which means you will be the one who has to catch the " + "mistake in them.", + ], + "about_team": "Business operations supports planning and performance management across the site.", + "min_qual_option1": "Option 1: Bachelor's degree in {degree_field} and {y1} years' experience in " + "business analysis or related area.", + "min_qual_option2": "Option 2: {y2} years' experience in business analysis or related area.", + "preferred": "Advanced SQL and visualization experience for {yp} years or more.", + "placements": [ + ("11500", "Full time", 66000, 122000, "Regular/Permanent", + ("business analytics, economics, or related area", 2, 4, 1), None), + ], + }, + { + "title": "Merchandising Intern", + "area": "students", + "category": "Internship", + "shifts": "WD", + "summary": "A paid summer internship inside a buying office, owning a real category project.", + "do": [ + "Merchandising Interns at {location_name} in {city}, {state} join a buying team for the summer " + "and own one category project end to end, from the data pull to the recommendation.", + "You will sit in supplier meetings, walk buildings with the field team, and present your " + "recommendation to merchandising leadership at the end of the program.", + ], + "about_team": "The internship program places students directly on the teams that make the " + "decisions.", + "min_qual_option1": "Option 1: Currently enrolled in a bachelor's degree program in {degree_field} " + "with an expected graduation date within {y1} years.", + "min_qual_option2": "Option 2: Currently enrolled in a master's degree program in {degree_field} " + "with an expected graduation date within {y2} years.", + "preferred": "Coursework or prior internship experience in retail merchandising within the last " + "{yp} years.", + "placements": [ + ("11109", "Intern", 64000, 90000, "Intern (Fixed Term)", + ("business, marketing, or supply chain", 2, 1, 2), None), + ("10101", "Intern", 66000, 92000, "Intern (Fixed Term)", + ("business administration or merchandising", 2, 1, 1), None), + ], + }, + { + "title": "Software Engineering Intern", + "area": "students", + "category": "Internship", + "shifts": "WD", + "summary": "A paid summer internship writing production code on a platform team.", + "do": [ + "Software Engineering Interns at {location_name} in {city}, {state} join a platform team, take " + "a real ticket in week one, and ship code to production before the summer is over.", + "You will have an engineering mentor, take part in code review, and present your project at " + "the end of the program.", + ], + "about_team": "Interns join the same teams and the same rituals as full-time engineers.", + "min_qual_option1": "Option 1: Currently enrolled in a bachelor's degree program in {degree_field} " + "with an expected graduation date within {y1} years.", + "min_qual_option2": "Option 2: Currently enrolled in a master's degree program in {degree_field} " + "with an expected graduation date within {y2} years.", + "preferred": "Coursework in data structures and algorithms and {yp} prior software internship.", + "placements": [ + ("11807", "Intern", 78000, 104000, "Intern (Fixed Term)", + ("computer science or computer engineering", 2, 1, 1), None), + ("10101", "Intern", 72000, 98000, "Intern (Fixed Term)", + ("computer science or information systems", 1, 2, 1), None), + ], + }, + { + "title": "Finance Intern", + "area": "students", + "category": "Internship", + "shifts": "WD", + "summary": "A paid summer internship on a finance planning team.", + "do": [ + "Finance Interns at {location_name} in {city}, {state} support a planning team through a full " + "forecast cycle and own one analysis that goes in front of a business leader.", + "You will learn the planning system, the reporting stack and how the company actually decides " + "where money goes.", + ], + "about_team": "Finance interns sit with the teams they support, not in a separate cohort room.", + "min_qual_option1": "Option 1: Currently enrolled in a bachelor's degree program in {degree_field} " + "with an expected graduation date within {y1} years.", + "min_qual_option2": "Option 2: Currently enrolled in a master's degree program in {degree_field} " + "with an expected graduation date within {y2} years.", + "preferred": "Coursework in financial modelling and {yp} prior finance internship.", + "placements": [ + ("10101", "Intern", 60000, 84000, "Intern (Fixed Term)", + ("finance, accounting, or economics", 2, 1, 1), None), + ], + }, + { + "title": "Data Analytics Intern", + "area": "students", + "category": "Internship", + "shifts": "WD", + "summary": "A paid summer internship on an analytics team supporting eCommerce.", + "do": [ + "Data Analytics Interns at {location_name} in {city}, {state} join an analytics team, build a " + "dashboard or model that a business owner asked for, and hand it over working.", + "You will use SQL and Python daily and present your findings at the end of the program.", + ], + "about_team": "Analytics here reports into the product organisation it supports.", + "min_qual_option1": "Option 1: Currently enrolled in a bachelor's degree program in {degree_field} " + "with an expected graduation date within {y1} years.", + "min_qual_option2": "Option 2: Currently enrolled in a master's degree program in {degree_field} " + "with an expected graduation date within {y2} years.", + "preferred": "Coursework in statistics or machine learning and {yp} prior analytics internship.", + "placements": [ + ("11003", "Intern", 70000, 96000, "Intern (Fixed Term)", + ("statistics, data science, or economics", 2, 1, 1), None), + ], + }, +] diff --git a/sites/walmart_careers/requirements.txt b/sites/walmart_careers/requirements.txt new file mode 100644 index 00000000..0ecb02b7 --- /dev/null +++ b/sites/walmart_careers/requirements.txt @@ -0,0 +1,5 @@ +Flask +Flask-SQLAlchemy +Flask-Login +Flask-WTF +Werkzeug diff --git a/sites/walmart_careers/seed_data.py b/sites/walmart_careers/seed_data.py new file mode 100644 index 00000000..d7cb69a3 --- /dev/null +++ b/sites/walmart_careers/seed_data.py @@ -0,0 +1,554 @@ +"""Deterministic seed for the Walmart Careers mirror. + +Run directly (`PYTHONHASHSEED=0 python seed_data.py`) to rebuild +`instance_seed/walmart_careers.db` from `catalog_source.py`. The build is +byte-reproducible: one RNG, no wall-clock reads, sorted iteration only, and +werkzeug password hashes hard-coded because werkzeug salts randomly. +""" +from __future__ import annotations + +import json +import os +import random +import shutil +from datetime import date, datetime, timedelta +from pathlib import Path + +os.environ.setdefault("WEBSYN_SKIP_BOOTSTRAP", "1") + +import catalog_source as source +from _content import MIRROR_REFERENCE_DATE +from app import ( + Application, + Area, + Category, + Job, + SavedJob, + Store, + User, + app, + confirmation_for, + db, + dumps_json, +) + +RNG = random.Random(20260905) +BASE_DIR = Path(__file__).resolve().parent +DB_PATH = BASE_DIR / "instance" / "walmart_careers.db" +INSTANCE_SEED_DIR = BASE_DIR / "instance_seed" + +# Hard-coded werkzeug hashes of DEMO_PASSWORD ("TestPass123!"). generate_password_hash +# salts randomly, so recomputing them here would break byte-identical rebuilds. +DEMO_PASSWORD_HASHES = { + "alice.j@test.com": "scrypt:32768:8:1$x9JMG7iKsrRO1AGh$e8e195799326a6e1ff55d4d20dd2735d9d68c0ed4879bd6b263ac33fa9953b0f6c8505680f1a1d8a9fbe9b0d01ef5e88f99b77b89fc30299fda217185a3b7acf", + "bob.c@test.com": "scrypt:32768:8:1$O14LIVdpqb3Q6D7E$4b9389cd00aad4417058fd629af5bf979b3f05bdf011791fbc65b7080fe898e50c7aedc2c22be92c71ae25a1df6922bb4ca44b386a7f17040b36888c0cdc8942", + "carol.d@test.com": "scrypt:32768:8:1$9PGtFS6I89BOEugS$890b1c1935bb6c0c4a6f7f5ad689cc02415e4bd03b02e101f0c2095931d4f157a8504c0fa4f12c3073c94e1480fea3305ffbadc5e8540c5eaf1c16965cb47be7", + "david.k@test.com": "scrypt:32768:8:1$luqs3gbiT1hPpw2c$09f31fef9514ae90d234cdd91a7f2c95d937e08a37fed60ce0b41755a097609040f7aa5083b94ecdd41804262dc778bd6f8d8e9f38f47f319fa8d6f3ed705d1b", +} + +BENCHMARK_USERS = [ + ("alice.j@test.com", "alice.j", "Alice Johnson", "Alice", "Johnson", "479-555-0134", "Bentonville", "AR"), + ("bob.c@test.com", "bob.c", "Bob Chen", "Bob", "Chen", "206-555-0178", "Seattle", "WA"), + ("carol.d@test.com", "carol.d", "Carol Davis", "Carol", "Davis", "253-555-0119", "Tacoma", "WA"), + ("david.k@test.com", "david.k", "David Kim", "David", "Kim", "214-555-0166", "Dallas", "TX"), +] +USER_CREATED_AT = datetime(2026, 6, 12, 9, 30, 0) + +# (user email, job title, store number) — resolved to job ids after the catalog is built. +SEED_SAVED_JOBS = [ + ("alice.j@test.com", "Freight Handler", "9046", datetime(2026, 8, 3, 14, 12, 0)), + ("alice.j@test.com", "Optician", "5991", datetime(2026, 8, 9, 10, 5, 0)), + ("alice.j@test.com", "Cosmetics Cashier", "2503", datetime(2026, 8, 17, 19, 41, 0)), + ("bob.c@test.com", "Asset Protection Associate", "5991", datetime(2026, 8, 4, 8, 22, 0)), + ("bob.c@test.com", "Class A CDL Truck Driver", "6038", datetime(2026, 8, 11, 16, 48, 0)), + ("bob.c@test.com", "Senior Data Scientist", "11500", datetime(2026, 8, 20, 12, 3, 0)), + ("carol.d@test.com", "Pharmacy Technician", "4137", datetime(2026, 8, 6, 7, 55, 0)), + ("carol.d@test.com", "Cafe Associate", "6318", datetime(2026, 8, 14, 21, 17, 0)), + ("david.k@test.com", "Merchandising and Stocking Associate", "4750", datetime(2026, 8, 8, 11, 26, 0)), + ("david.k@test.com", "IT Support Engineer", "10101", datetime(2026, 8, 19, 15, 34, 0)), +] + +# (user email, job title, store number, submitted_at) +SEED_APPLICATIONS = [ + ("alice.j@test.com", "Team Lead", "144", datetime(2026, 8, 5, 13, 20, 0)), + ("bob.c@test.com", "Order Filler", "6014", datetime(2026, 8, 12, 9, 2, 0)), + ("carol.d@test.com", "Optician", "2073", datetime(2026, 8, 16, 17, 44, 0)), + ("david.k@test.com", "Financial Analyst III", "11500", datetime(2026, 8, 21, 10, 11, 0)), +] + +HERO_SETS = { + "salaried": ["jobhero-corp-1.jpg", "jobhero-corp-2.jpg", "jobhero-corp-3.jpg"], + "sams": ["jobhero-sams-1.png", "jobhero-sams-2.jpg", "jobhero-wm-2.jpg"], + "walmart": ["jobhero-wm-1.png", "jobhero-wm-3.jpg", "jobhero-wm-4.jpg"], + "walmart-alt": ["jobhero-wm-4.jpg", "jobhero-wm-2.jpg", "jobhero-wm-1.png"], +} + +HOURLY_CLOSING = ( + "At Walmart, we offer competitive pay as well as performance-based incentive awards and other " + "great benefits for a happier mind, body, and wallet. Health benefits include medical, vision and " + "dental coverage. Financial benefits include 401(k), stock purchase and company-paid life " + "insurance. Paid time off benefits include parental leave, family care leave, bereavement, jury " + "duty, and voting." +) +LBU_CLOSING = ( + "Live Better U is a Walmart-paid education benefit program for full-time and part-time associates " + "in Walmart and Sam's Club facilities. Programs range from high school completion to bachelor's " + "degrees, including English Language Learning and short-form certificates. Tuition, books, and " + "fees are completely paid for by Walmart." +) + + +# --------------------------------------------------------------------------- # +# Catalog construction +# --------------------------------------------------------------------------- # +def _shift_names(codes: str) -> list[str]: + return [source.SHIFT_CODES[c] for c in codes.split(",")] + + +def _build_areas() -> dict[str, Area]: + areas: dict[str, Area] = {} + for slug, name, order, blurb, hero, has_index, filterable in source.AREAS: + area = Area( + slug=slug, + name=name, + display_order=order, + blurb=blurb, + hero_image=hero, + has_index_page=has_index, + is_filterable=filterable, + ) + db.session.add(area) + areas[slug] = area + db.session.flush() + return areas + + +def _build_categories(areas: dict[str, Area]) -> dict[tuple[str, str], Category]: + categories: dict[tuple[str, str], Category] = {} + for area_slug, name, slug, order in source.CATEGORIES: + category = Category( + area_id=areas[area_slug].id, name=name, slug=slug, display_order=order + ) + db.session.add(category) + categories[(area_slug, name)] = category + db.session.flush() + return categories + + +def _build_stores() -> dict[str, Store]: + stores: dict[str, Store] = {} + for row in source.STORES: + (number, banner, location_name, street, city, state, zip_code, + lat, lng, is_hub, is_office, _brand) = row + store = Store( + store_number=number, + banner=banner, + location_name=location_name, + street=street, + city=city, + state=state, + zip=zip_code, + lat=lat, + lng=lng, + is_hub=is_hub, + is_office=is_office, + ) + db.session.add(store) + stores[number] = store + db.session.flush() + return stores + + +def _store_brand() -> dict[str, str]: + return {row[0]: row[11] for row in source.STORES} + + +def _hero_for(population: str, brand: str, index: int) -> list[str]: + if population == "salaried": + pool = HERO_SETS["salaried"] + elif brand == "Sam's Club": + pool = HERO_SETS["sams"] + elif index % 2: + pool = HERO_SETS["walmart-alt"] + else: + pool = HERO_SETS["walmart"] + return list(pool) + + +def _build_jobs(areas, categories, stores) -> list[Job]: + brands = _store_brand() + used_ids: set[str] = set() + for family in source.HOURLY_FAMILIES: + for placement in family["placements"]: + extras = placement[6] or {} + if "job_id" in extras: + used_ids.add(extras["job_id"]) + for family in source.SALARIED_FAMILIES: + for placement in family["placements"]: + extras = placement[6] or {} + if "job_id" in extras: + used_ids.add(extras["job_id"]) + + jobs: list[Job] = [] + store_cursor: dict[str, int] = {} + index = 0 + + for family in source.HOURLY_FAMILIES: + area = areas[family["area"]] + category = categories[(family["area"], family["category"])] + for placement in family["placements"]: + store_no, emp_type, codes, min_pay, max_pay, positions, extras = placement + extras = extras or {} + store = stores[store_no] + brand = brands[store_no] + cursor = store_cursor.get(store_no, 10200) + RNG.randint(120, 980) + job_id = extras.get("job_id") + if job_id is None: + job_id = f"CP-{store_no}-{cursor}" + while job_id in used_ids: + cursor += 37 + job_id = f"CP-{store_no}-{cursor}" + store_cursor[store_no] = cursor + used_ids.add(job_id) + + fmt = { + "banner": store.banner, + "store": store.store_number, + "city": store.city, + "state": store.state, + "location_name": store.location_name, + } + paragraphs = [p.format(**fmt) for p in family["do"]] + paragraphs.append(HOURLY_CLOSING) + paragraphs.append(LBU_CLOSING) + primary_code = codes.split(",")[0] + job = Job( + job_id=job_id, + population="hourly", + title=family["title"], + brand=brand, + store_id=store.id, + area_id=area.id, + category_id=category.id, + shifts_json=dumps_json(_shift_names(codes)), + employment_type=emp_type, + pay_frequency="Hourly", + min_pay=min_pay, + max_pay=max_pay, + posted_date=MIRROR_REFERENCE_DATE - timedelta(days=RNG.randint(1, 120)), + sort_rank=0, + summary=family["summary"].format(**fmt), + description="\n\n".join(paragraphs), + additional_description_json=dumps_json( + [b.format(**fmt) for b in family["bring"]] + ), + hashtag=family.get("hashtag"), + shift_time=extras.get("shift_time", source.SHIFT_WINDOWS[primary_code]), + positions_available=positions, + min_age_note=emp_type != "Intern", + hero_images_json=dumps_json(_hero_for("hourly", brand, index)), + ) + db.session.add(job) + jobs.append(job) + index += 1 + + salaried_cursor = 2410000 + posting_seq = 5210000 + for family in source.SALARIED_FAMILIES: + area = areas[family["area"]] + category = categories[(family["area"], family["category"])] + for placement in family["placements"]: + store_no, emp_type, min_pay, max_pay, worker_type, slots, extras = placement + extras = extras or {} + store = stores[store_no] + brand = brands[store_no] + salaried_cursor += RNG.randint(150, 900) + job_id = extras.get("job_id") + if job_id is None: + job_id = f"R-{salaried_cursor}" + while job_id in used_ids: + salaried_cursor += 13 + job_id = f"R-{salaried_cursor}" + used_ids.add(job_id) + posting_seq += RNG.randint(400, 4000) + + degree_field, y1, y2, yp = slots + fmt = { + "banner": store.banner, + "store": store.store_number, + "city": store.city, + "state": store.state, + "location_name": store.location_name, + "degree_field": degree_field, + "y1": y1, + "y2": y2, + "yp": yp, + } + paragraphs = [p.format(**fmt) for p in family["do"]] + paragraphs.append("About Team: " + family["about_team"].format(**fmt)) + job = Job( + job_id=job_id, + population="salaried", + title=family["title"], + brand=brand, + store_id=store.id, + area_id=area.id, + category_id=category.id, + shifts_json=dumps_json(_shift_names(family["shifts"])), + employment_type=emp_type, + pay_frequency="Annual", + min_pay=min_pay, + max_pay=max_pay, + posted_date=MIRROR_REFERENCE_DATE - timedelta(days=RNG.randint(1, 120)), + sort_rank=0, + summary=family["summary"].format(**fmt), + description="\n\n".join(paragraphs), + additional_description_json=None, + hashtag=None, + shift_time=None, + positions_available=None, + min_age_note=False, + worker_type=worker_type, + job_posting_id=f"JOB_POSTING-3-{posting_seq}", + min_qualifications_json=dumps_json( + [ + family["min_qual_option1"].format(**fmt), + family["min_qual_option2"].format(**fmt), + ] + ), + preferred_qualifications=family["preferred"].format(**fmt), + hero_images_json=dumps_json(_hero_for("salaried", brand, index)), + ) + db.session.add(job) + jobs.append(job) + index += 1 + + ranks = list(range(len(jobs))) + RNG.shuffle(ranks) + for job, rank in zip(jobs, ranks): + job.sort_rank = rank + db.session.flush() + return jobs + + +# --------------------------------------------------------------------------- # +# Seed entry points (each gated as a whole — see AGENTS.md "Idempotent seeding") +# --------------------------------------------------------------------------- # +def seed_database(force: bool = False) -> None: + if Job.query.count() > 0 and not force: + return + areas = _build_areas() + categories = _build_categories(areas) + stores = _build_stores() + _build_jobs(areas, categories, stores) + db.session.commit() + + +def seed_benchmark_users(force: bool = False) -> None: + if User.query.filter_by(email="alice.j@test.com").first() and not force: + return + users: dict[str, User] = {} + for email, username, display, first, last, phone, city, state in BENCHMARK_USERS: + user = User( + email=email, + username=username, + display_name=display, + first_name=first, + last_name=last, + phone=phone, + city=city, + state=state, + password_hash=DEMO_PASSWORD_HASHES[email], + created_at=USER_CREATED_AT, + ) + db.session.add(user) + users[email] = user + db.session.flush() + + for email, title, store_number, saved_at in SEED_SAVED_JOBS: + job = _find_job(title, store_number) + db.session.add(SavedJob(user_id=users[email].id, job_id=job.job_id, saved_at=saved_at)) + + for email, title, store_number, submitted_at in SEED_APPLICATIONS: + job = _find_job(title, store_number) + user = users[email] + application = Application( + job_id=job.job_id, + user_id=user.id, + email=user.email, + first_name=user.first_name, + last_name=user.last_name, + phone=user.phone, + status="Submitted", + confirmation_no="pending", + submitted_at=submitted_at, + ) + db.session.add(application) + db.session.flush() + application.confirmation_no = confirmation_for(application.id) + + db.session.commit() + + +def _find_job(title: str, store_number: str) -> Job: + store = Store.query.filter_by(store_number=store_number).one() + job = ( + Job.query.filter_by(title=title, store_id=store.id) + .order_by(Job.job_id) + .first() + ) + if job is None: + raise RuntimeError(f"no seeded job {title!r} at store {store_number}") + return job + + +# --------------------------------------------------------------------------- # +# Build-time invariants. Only ever called from build_seed_database(). +# --------------------------------------------------------------------------- # +def _assert_distractors() -> None: + from app import Job as J, current_filters # noqa: F401 (kept for symmetry) + from app import search_jobs + + problems: list[str] = [] + + def base(**kwargs) -> dict: + filters = { + "q": "", "area": [], "category": [], "brand": [], "shift": [], + "type": [], "rate": [], "loc": "", "radius": 25, + "sort": "relevance", "page": 1, "tab": "jobs", + } + filters.update(kwargs) + return filters + + def results(**kwargs) -> list[Job]: + jobs, _store, _failed = search_jobs(base(**kwargs)) + return jobs + + # --- structural volumes ------------------------------------------------ + if Job.query.count() != 200: + problems.append(f"expected 200 jobs, found {Job.query.count()}") + for category in Category.query.all(): + count = Job.query.filter_by(category_id=category.id).count() + if count < 4: + problems.append(f"category {category.name!r} has only {count} jobs") + for store in Store.query.all(): + count = Job.query.filter_by(store_id=store.id).count() + if count < 3: + problems.append(f"store #{store.store_number} has only {count} jobs") + states = {s.state for s in Store.query.all()} + for state in sorted(states): + count = Job.query.join(Store).filter(Store.state == state).count() + if count < 8: + problems.append(f"state {state} has only {count} jobs") + shift_counts = {name: 0 for name in source.SHIFT_CODES.values()} + for job in Job.query.all(): + for name in job.shifts: + shift_counts[name] += 1 + for name, count in sorted(shift_counts.items()): + if count < 24: + problems.append(f"shift {name!r} appears on only {count} jobs") + + # --- unique-answer invariants used by the task set --------------------- + pr_cashiers = [ + j for j in Job.query.join(Store).filter(Store.state == "PR").all() + if "Cashier" in j.title and j.population == "hourly" + ] + with_weekday_day = [j for j in pr_cashiers if "Weekday Day" in j.shifts] + if len(pr_cashiers) < 5: + problems.append(f"only {len(pr_cashiers)} PR cashier postings") + if len(with_weekday_day) < 3: + problems.append("fewer than 3 PR cashier postings list Weekday Day") + tops = sorted((j.positions_available for j in with_weekday_day), reverse=True) + if len(tops) >= 2 and tops[0] == tops[1]: + problems.append("PR Weekday Day cashier postings have a tied maximum of open positions") + if any(j.positions_available >= tops[0] for j in pr_cashiers if j not in with_weekday_day): + pass # a higher-positions near-miss without Weekday Day is intentional + + hoboken_tech = [ + j for j in results(area=["technology"], type=["Full time"], loc="Hoboken, NJ", radius=25) + ] + if len(hoboken_tech) < 6: + problems.append(f"only {len(hoboken_tech)} Full time Technology roles near Hoboken") + over_200k = [j for j in hoboken_tech if float(j.max_pay) > 200000] + if len(over_200k) != 1: + problems.append(f"{len(over_200k)} Hoboken Technology roles top out above $200,000 (want 1)") + + sams_pt_overnight = results(brand=["Sam's Club"], type=["Part time"], shift=["Weekend Overnight"]) + if len(sams_pt_overnight) < 6: + problems.append( + f"only {len(sams_pt_overnight)} Sam's Club Part time Weekend Overnight roles" + ) + cheap_tx = [ + j for j in sams_pt_overnight + if float(j.max_pay) <= 20.00 and j.store.state == "TX" + ] + if len(cheap_tx) != 1: + problems.append(f"{len(cheap_tx)} Sam's Club PT overnight TX roles at or under $20/hr (want 1)") + full_matches = [j for j in sams_pt_overnight if float(j.max_pay) <= 20.00] + if len(full_matches) > len(sams_pt_overnight) / 2: + problems.append("more than half of the Sam's Club overnight results match every constraint") + + cleveland = results(loc="Cleveland, OH", radius=25) + if len(cleveland) < 6: + problems.append(f"only {len(cleveland)} roles within 25 miles of Cleveland, OH") + + ms_auto = [ + j for j in Job.query.join(Store).filter(Store.state == "MS").all() + if j.title == "Auto Care Center Technician" + ] + if len(ms_auto) != 2: + problems.append(f"{len(ms_auto)} Auto Care Center Technician postings in MS (want 2)") + elif ms_auto[0].positions_available == ms_auto[1].positions_available: + problems.append("the two MS Auto Care postings have the same number of open positions") + + marcy_freight = [ + j for j in Job.query.join(Store).filter(Store.city == "Marcy").all() + if j.title == "Freight Handler" + ] + if len(marcy_freight) != 2: + problems.append(f"{len(marcy_freight)} Freight Handler postings in Marcy, NY (want 2)") + elif marcy_freight[0].shift_time == marcy_freight[1].shift_time: + problems.append("the two Marcy Freight Handler postings share a shift start window") + + last_mile = Job.query.filter(Job.title.like("Senior Manager, Delivery Search%")).all() + if len(last_mile) != 2: + problems.append(f"{len(last_mile)} Last Mile Delivery postings (want 2)") + else: + options = [j.min_qualifications[1] for j in last_mile] + if options[0] == options[1]: + problems.append("the two Last Mile Delivery postings share Option 2 text") + + # Qualification text referenced by tasks must be unique per posting. + quals = [j.min_qualifications_json for j in Job.query.filter_by(population="salaried").all()] + if len(set(quals)) != len(quals): + problems.append("salaried minimum-qualification texts are not unique per posting") + + # Targets must not be pinned to rank 1 of their own natural query. + for query, title in (("optician", "Optician"), ("freight handler", "Freight Handler")): + rows = results(q=query) + if len(rows) < 6: + problems.append(f"query {query!r} returns only {len(rows)} results") + + if problems: + raise AssertionError( + "seed distractor checks failed:\n - " + "\n - ".join(problems) + ) + + +def build_seed_database() -> None: + INSTANCE_SEED_DIR.mkdir(parents=True, exist_ok=True) + DB_PATH.parent.mkdir(parents=True, exist_ok=True) + if DB_PATH.exists(): + DB_PATH.unlink() + with app.app_context(): + db.drop_all() + db.create_all() + seed_database(force=True) + seed_benchmark_users(force=True) + _assert_distractors() + shutil.copyfile(DB_PATH, INSTANCE_SEED_DIR / "walmart_careers.db") + + +if __name__ == "__main__": + build_seed_database() + print("Seed database generated from the deterministic Walmart Careers source catalog.") diff --git a/sites/walmart_careers/static/css/site.css b/sites/walmart_careers/static/css/site.css new file mode 100644 index 00000000..d2800a77 --- /dev/null +++ b/sites/walmart_careers/static/css/site.css @@ -0,0 +1,343 @@ +/* Walmart Careers mirror — Living Design tokens pulled from the live site CSS. */ +@font-face { + font-family: "EverydaySansUI"; + src: url("../fonts/EverydaySansUI-wght.ttf") format("truetype-variations"); + font-weight: 100 900; + font-display: swap; +} + +:root { + --ld-blue-100: #0053e2; + --ld-blue-130: #002e99; + --ld-blue-160: #001e60; + --ld-blue-10: #e6f1fc; + --ld-blue-9: #e9f1fe; + --ld-spark-100: #ffc220; + --ld-gray-200: #f1f1f2; + --ld-gray-20: #e3e4e5; + --ld-gray-5: #f8f8f8; + --ld-gray-100: #74767c; + --ld-text-subtle: #515357; + --ld-red: #de1c24; + --ld-green: #2a8703; + --ld-sams: #00358e; + --header-h: 80px; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + font-family: "EverydaySansUI", "Bogle", Arial, Helvetica, sans-serif; + font-size: 16px; + line-height: 1.45; + color: var(--ld-blue-160); + background: #fff; +} + +a { color: var(--ld-blue-100); } +a:hover { color: var(--ld-blue-130); } + +img { max-width: 100%; } + +.skip-link { + position: absolute; left: -9999px; top: 0; background: #fff; color: var(--ld-blue-160); + padding: 8px 16px; z-index: 100; +} +.skip-link:focus { left: 8px; top: 8px; } + +.wrap { max-width: 1360px; margin: 0 auto; padding: 0 32px; } +.wrap-narrow { max-width: 900px; margin: 0 auto; padding: 0 32px; } + +/* ------------------------------ header ---------------------------------- */ +.site-header { + background: var(--ld-blue-100); + min-height: var(--header-h); + display: flex; align-items: center; + position: sticky; top: 0; z-index: 50; +} +.site-header .wrap { display: flex; align-items: center; gap: 24px; width: 100%; } +.brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; text-decoration: none; } +.brand img { height: 34px; width: auto; } +.brand span { color: #fff; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; } +.main-nav { display: flex; gap: 22px; flex: 1 1 auto; } +.main-nav a { + color: #fff; text-decoration: none; font-size: 16px; padding: 8px 2px; + border-bottom: 2px solid transparent; +} +.main-nav a:hover, .main-nav a.active { border-bottom-color: var(--ld-spark-100); color: #fff; } + +.header-search { display: flex; align-items: center; flex: 0 0 auto; } +.header-search form { display: flex; align-items: center; background: #fff; border-radius: 999px; padding: 4px 4px 4px 18px; } +.header-search input { + border: 0; outline: none; width: 240px; font-size: 15px; font-family: inherit; + color: var(--ld-blue-160); background: transparent; +} +.header-search button { + border: 0; background: var(--ld-blue-100); color: #fff; width: 40px; height: 40px; + border-radius: 999px; cursor: pointer; display: grid; place-items: center; +} +.header-search button img { width: 18px; height: 18px; filter: brightness(0) invert(1); } + +.user-menu { flex: 0 0 auto; } +.user-links { display: flex; gap: 16px; align-items: center; } +.user-links a { color: #fff; text-decoration: none; font-size: 15px; } +.user-links a:hover { text-decoration: underline; color: #fff; } +.user-links form { margin: 0; } +.avatar { + width: 34px; height: 34px; border-radius: 999px; background: var(--ld-spark-100); + color: var(--ld-blue-160); display: grid; place-items: center; font-weight: 700; font-size: 14px; +} +.linkish { + background: none; border: 0; color: #fff; font: inherit; cursor: pointer; + text-decoration: underline; padding: 0; +} + +/* ------------------------------ buttons --------------------------------- */ +.btn { + display: inline-block; border: 0; border-radius: 999px; cursor: pointer; + font: inherit; font-weight: 600; padding: 12px 28px; text-decoration: none; + background: var(--ld-blue-100); color: #fff; +} +.btn:hover { background: var(--ld-blue-130); color: #fff; } +.btn-secondary { background: #fff; color: var(--ld-blue-160); border: 1px solid var(--ld-blue-160); } +.btn-secondary:hover { background: var(--ld-gray-200); color: var(--ld-blue-160); } +.btn-spark { background: var(--ld-spark-100); color: var(--ld-blue-160); } +.btn-spark:hover { background: #ffd45c; color: var(--ld-blue-160); } +.btn-sm { padding: 7px 18px; font-size: 14px; } + +/* ------------------------------ hero ------------------------------------ */ +.hero { background: var(--ld-blue-100); color: #fff; padding: 0; } +.hero .inner { padding: 64px 0 72px; } +.hero h1 { font-size: 56px; line-height: 1.05; font-weight: 400; margin: 0 0 32px; max-width: 720px; } +.hero h1 span { display: block; } +.hero-search { display: flex; background: #fff; border-radius: 999px; padding: 8px 8px 8px 28px; max-width: 720px; } +.hero-search input { flex: 1; border: 0; outline: none; font-size: 18px; font-family: inherit; color: var(--ld-blue-160); } +.hero-search button { border: 0; background: var(--ld-blue-100); color: #fff; border-radius: 999px; padding: 14px 34px; font: inherit; font-weight: 600; cursor: pointer; } +.hero-photo { display: block; width: 100%; height: 320px; object-fit: cover; } + +/* ------------------------------ sections -------------------------------- */ +section { padding: 56px 0; } +section h2 { font-size: 34px; font-weight: 400; margin: 0 0 28px; } +section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } +.section-alt { background: var(--ld-gray-5); } +.section-blue { background: var(--ld-blue-160); color: #fff; } +.section-blue h2, .section-blue a { color: #fff; } + +.carousel { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; } +.carousel a { + display: block; text-decoration: none; color: var(--ld-blue-160); + border-radius: 24px; overflow: hidden; background: var(--ld-blue-10); +} +.carousel img { display: block; width: 100%; height: 220px; object-fit: cover; } +.carousel .cap { padding: 16px 18px 20px; } +.carousel .cap b { display: block; font-size: 18px; } +.carousel .cap em { display: block; font-style: normal; color: var(--ld-text-subtle); font-size: 14px; } +.carousel .cap i { display: block; font-style: normal; margin-top: 8px; color: var(--ld-blue-100); font-weight: 600; font-size: 14px; } + +.ribbon { display: flex; flex-wrap: wrap; gap: 12px; } +.ribbon a { + background: var(--ld-blue-10); color: var(--ld-blue-160); text-decoration: none; + padding: 14px 26px; border-radius: 999px; font-weight: 600; +} +.ribbon a:hover { background: var(--ld-blue-9); color: var(--ld-blue-130); } + +.benefit-list { display: grid; gap: 4px; } +.benefit-row { display: flex; gap: 18px; align-items: flex-start; padding: 18px 0; border-bottom: 1px solid var(--ld-gray-20); } +.benefit-row img { width: 40px; height: 40px; } +.benefit-row b { display: block; font-size: 18px; } +.benefit-row span { color: var(--ld-text-subtle); } + +.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } +.stat-card { background: var(--ld-blue-10); border-radius: 24px; padding: 28px; } +.stat-card b { display: block; font-size: 34px; font-weight: 700; color: var(--ld-blue-100); } + +.tile-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } +.tile { border-radius: 24px; overflow: hidden; background: var(--ld-gray-5); } +.tile img { display: block; width: 100%; height: 200px; object-fit: cover; } +.tile .cap { padding: 16px 18px; } + +.bento { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; align-items: stretch; } +.bento .card { border-radius: 24px; overflow: hidden; background: var(--ld-blue-10); } +.bento .card img { display: block; width: 100%; height: 100%; min-height: 260px; object-fit: cover; } +.bento .values { padding: 28px; } +.bento .values ul { margin: 0; padding-left: 18px; } +.bento .values li { margin-bottom: 10px; } + +/* ------------------------------ job cards ------------------------------- */ +.job-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } +.job-grid.one-col { grid-template-columns: 1fr; } +.job-card { + border: 1px solid var(--ld-gray-20); border-radius: 32px; padding: 24px; + background: #fff; display: flex; gap: 16px; align-items: flex-start; +} +.job-card:hover { border-color: var(--ld-blue-130); } +.job-card .spark { width: 25px; height: 25px; flex: 0 0 25px; margin-top: 4px; } +.job-card .body { flex: 1; min-width: 0; } +.job-card h3 { margin: 0 0 6px; font-size: 18px; font-weight: 700; } +.job-card h3 a { color: var(--ld-blue-160); text-decoration: none; } +.job-card h3 a:hover { text-decoration: underline; } +.job-card .meta { font-size: 16px; color: var(--ld-blue-160); } +.job-card .meta div { margin-bottom: 2px; } +.job-card .pay { margin-top: 8px; font-size: 16px; } +.job-card .actions { margin-top: 14px; display: flex; gap: 10px; align-items: center; } + +/* ------------------------------ results --------------------------------- */ +.results-layout { display: grid; grid-template-columns: 340px 1fr; gap: 32px; padding: 32px 0 64px; } +.results-aside .panel { background: var(--ld-gray-5); border-radius: 24px; padding: 20px; margin-bottom: 20px; } +.results-aside .panel h3 { font-size: 18px; } +.cluster-map { display: block; border-radius: 16px; } +.pin-card { display: block; border-radius: 16px; } + +.filter-panel fieldset { border: 0; padding: 0; margin: 0 0 18px; } +.filter-panel legend { font-weight: 700; padding: 0 0 8px; font-size: 16px; } +.filter-panel label { display: flex; gap: 8px; align-items: center; font-size: 15px; padding: 3px 0; cursor: pointer; } +.filter-panel .cat-group { margin: 4px 0 10px 8px; } +.filter-panel .cat-group summary { cursor: pointer; font-size: 15px; padding: 3px 0; } +.filter-actions { display: flex; gap: 10px; margin-top: 12px; } + +.results-head { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; flex-wrap: wrap; } +.results-head h1 { font-size: 32px; font-weight: 300; margin: 0; } +.tabs { display: flex; gap: 28px; border-bottom: 1px solid var(--ld-gray-20); margin: 16px 0 24px; } +.tabs a { + text-decoration: none; color: var(--ld-text-subtle); padding: 10px 2px; + border-bottom: 3px solid transparent; font-weight: 600; +} +.tabs a.active { color: var(--ld-blue-100); border-bottom-color: var(--ld-blue-100); } +.sort-row { display: flex; gap: 16px; align-items: center; margin-bottom: 20px; font-size: 15px; } +.empty-panel { background: var(--ld-blue-9); border-radius: 24px; padding: 32px; } + +.pagination { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 32px; align-items: center; } +.pagination a, .pagination span { + min-width: 40px; height: 40px; border-radius: 999px; display: grid; place-items: center; + text-decoration: none; border: 1px solid var(--ld-gray-20); color: var(--ld-blue-160); padding: 0 12px; +} +.pagination .current { background: var(--ld-blue-100); color: #fff; border-color: var(--ld-blue-100); } + +/* ------------------------------ job detail ------------------------------ */ +.job-hero { background: var(--ld-blue-100); color: #fff; padding: 40px 0 0; } +.job-hero h1 { font-size: 40px; font-weight: 400; margin: 0 0 8px; } +.job-hero .loc { font-size: 18px; margin-bottom: 20px; } +.job-hero .hero-actions { display: flex; gap: 14px; align-items: center; margin-bottom: 28px; } +.job-hero .photos { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } +.job-hero .photos img { display: block; width: 100%; height: 190px; object-fit: cover; } + +.detail-layout { display: grid; grid-template-columns: 230px 1fr 330px; gap: 36px; padding: 36px 0 64px; } +.detail-nav { position: sticky; top: 100px; align-self: start; } +.detail-nav ul { list-style: none; margin: 0; padding: 0; } +.detail-nav li { padding: 6px 0; } +.detail-nav a { text-decoration: none; color: var(--ld-blue-160); } +.detail-nav .sub { padding-left: 16px; font-size: 15px; } + +.fact-card { border: 1px solid var(--ld-gray-20); border-radius: 24px; padding: 22px; position: sticky; top: 100px; } +.fact-card h2 { font-size: 20px; margin: 0 0 12px; } +.fact-card address { font-style: normal; line-height: 1.5; margin-bottom: 12px; } +.fact-card .positions { + display: inline-block; background: var(--ld-blue-10); border-radius: 999px; + padding: 6px 14px; font-size: 14px; font-weight: 600; margin-bottom: 10px; +} +.fact-card .req-id { font-size: 14px; color: var(--ld-text-subtle); margin-bottom: 14px; } +.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 8px; } +.chip { + background: var(--ld-blue-160); color: #fff; border-radius: 999px; + padding: 8px 16px; font-size: 14px; font-weight: 600; +} +.footnote { font-size: 13px; color: var(--ld-text-subtle); } + +.detail-body h2 { font-size: 26px; font-weight: 400; margin: 32px 0 12px; } +.detail-body h2:first-child { margin-top: 0; } +.detail-body p { margin: 0 0 14px; } +.detail-body ul { margin: 0 0 16px; padding-left: 20px; } +.detail-body li { margin-bottom: 8px; } +.hashtag { font-weight: 700; color: var(--ld-blue-100); } +.legal { font-size: 13px; color: var(--ld-text-subtle); margin-top: 20px; } + +.benefit-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } +.benefit-tile { background: var(--ld-blue-160); color: #fff; border-radius: 24px; padding: 26px; } +.benefit-tile img { width: 44px; height: 44px; margin-bottom: 12px; } +.benefit-tile b { display: block; font-size: 20px; } +.benefit-tile em { font-style: normal; display: block; color: var(--ld-spark-100); margin-bottom: 10px; } + +.quote-band { background: var(--ld-blue-100); color: #fff; border-radius: 24px; padding: 32px; font-size: 22px; } + +/* ------------------------------ forms ----------------------------------- */ +.form-card { max-width: 520px; margin: 48px auto; border: 1px solid var(--ld-gray-20); border-radius: 24px; padding: 32px; } +.form-wide { max-width: 720px; } +.form-card h1 { font-size: 28px; font-weight: 400; margin: 0 0 20px; } +.field { margin-bottom: 16px; } +.field label { display: block; font-weight: 600; margin-bottom: 6px; font-size: 15px; } +.field input[type=text], .field input[type=email], .field input[type=password], .field input[type=tel], .field select { + width: 100%; padding: 12px 14px; border: 1px solid var(--ld-gray-20); border-radius: 12px; + font: inherit; background: var(--ld-gray-5); color: var(--ld-blue-160); +} +.field .check { display: flex; gap: 10px; align-items: flex-start; font-weight: 400; } +.errors { background: #fdecec; border: 1px solid var(--ld-red); color: var(--ld-red); border-radius: 12px; padding: 14px 18px; margin-bottom: 18px; } +.errors ul { margin: 0; padding-left: 18px; } +.flash { border-radius: 12px; padding: 12px 18px; margin: 16px 0; } +.flash.success { background: #eaf6e6; border: 1px solid var(--ld-green); color: #1c5c02; } +.flash.info { background: var(--ld-blue-9); border: 1px solid var(--ld-blue-100); } +.flash.warning { background: #fff5e0; border: 1px solid var(--ld-spark-100); color: #7a5a00; } +.form-note { font-size: 14px; color: var(--ld-text-subtle); margin-top: 14px; } + +.review-list { list-style: none; margin: 0 0 22px; padding: 0; } +.review-list li { display: flex; justify-content: space-between; gap: 20px; padding: 10px 0; border-bottom: 1px solid var(--ld-gray-20); } +.review-list b { font-weight: 600; } +.confirmation { + background: var(--ld-blue-10); border-radius: 24px; padding: 28px; margin: 24px 0; + font-size: 22px; font-weight: 700; +} + +table.data { width: 100%; border-collapse: collapse; } +table.data th, table.data td { text-align: left; padding: 12px 10px; border-bottom: 1px solid var(--ld-gray-20); } +table.data th { font-size: 14px; text-transform: uppercase; letter-spacing: .04em; color: var(--ld-text-subtle); } + +/* ------------------------------ area / locations ------------------------ */ +.area-hero { position: relative; color: #fff; } +.area-hero img { width: 100%; height: 360px; object-fit: cover; display: block; } +.area-hero .overlay { + position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0,30,96,.88), rgba(0,30,96,.32)); + display: flex; align-items: center; +} +.area-hero h1 { font-size: 46px; font-weight: 400; margin: 0 0 14px; } +.area-hero p { max-width: 620px; font-size: 18px; margin: 0 0 22px; } +.category-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } +.category-grid a { + display: flex; justify-content: space-between; gap: 12px; text-decoration: none; + border: 1px solid var(--ld-gray-20); border-radius: 16px; padding: 18px 22px; color: var(--ld-blue-160); +} +.category-grid a:hover { border-color: var(--ld-blue-130); background: var(--ld-gray-5); } +.category-grid .count { color: var(--ld-text-subtle); font-size: 15px; } + +.hub-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; } +.hub-card { border: 1px solid var(--ld-gray-20); border-radius: 24px; overflow: hidden; } +.hub-card img { display: block; width: 100%; height: 220px; object-fit: cover; } +.hub-card .cap { padding: 22px; } + +.faq details { border-bottom: 1px solid var(--ld-gray-20); padding: 14px 0; } +.faq summary { cursor: pointer; font-weight: 600; font-size: 17px; } +.faq p { margin: 10px 0 0; color: var(--ld-text-subtle); } +.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } +.steps .step { background: var(--ld-blue-10); border-radius: 24px; padding: 24px; } + +/* ------------------------------ footer ---------------------------------- */ +.site-footer { background: var(--ld-blue-160); color: #fff; padding: 72px 0 40px; margin-top: 40px; } +.site-footer a { color: #fff; text-decoration: none; } +.site-footer a:hover { text-decoration: underline; color: #fff; } +.footer-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; margin-bottom: 40px; } +.footer-cols h4 { font-size: 17px; margin: 0 0 14px; } +.footer-cols ul { list-style: none; margin: 0; padding: 0; } +.footer-cols li { margin-bottom: 9px; font-size: 15px; } +.social { display: flex; gap: 16px; margin-bottom: 28px; } +.social img { width: 26px; height: 26px; } +.legal-text { font-size: 13px; color: #cfd9ea; margin-bottom: 16px; } +.footer-bottom { display: flex; gap: 22px; flex-wrap: wrap; font-size: 14px; border-top: 1px solid rgba(255,255,255,.2); padding-top: 22px; } + +@media (max-width: 1100px) { + .results-layout, .detail-layout { grid-template-columns: 1fr; } + .carousel, .stat-grid, .tile-grid, .steps, .footer-cols { grid-template-columns: repeat(2, 1fr); } + .job-grid, .category-grid, .benefit-tiles, .hub-grid, .bento { grid-template-columns: 1fr; } + .detail-nav, .fact-card { position: static; } + .hero h1 { font-size: 40px; } +} diff --git a/sites/walmart_careers/static/fonts/EverydaySansUI-wght.ttf b/sites/walmart_careers/static/fonts/EverydaySansUI-wght.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7b25800845c3c8c5269427fa593db32825660768 GIT binary patch literal 166468 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 z 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�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 literal 0 HcmV?d00001 diff --git a/sites/walmart_careers/static/fonts/LivingDesign.woff b/sites/walmart_careers/static/fonts/LivingDesign.woff new file mode 100644 index 0000000000000000000000000000000000000000..f6ca2a57f42e761608229e352dbd08bc4d9766d3 GIT binary patch literal 10548 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/benefit-growth.svg b/sites/walmart_careers/static/icons/benefit-growth.svg new file mode 100644 index 00000000..69f7fdc2 --- /dev/null +++ b/sites/walmart_careers/static/icons/benefit-growth.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/benefit-health.svg b/sites/walmart_careers/static/icons/benefit-health.svg new file mode 100644 index 00000000..ccccbd9a --- /dev/null +++ b/sites/walmart_careers/static/icons/benefit-health.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/benefit-pto.svg b/sites/walmart_careers/static/icons/benefit-pto.svg new file mode 100644 index 00000000..5df7085e --- /dev/null +++ b/sites/walmart_careers/static/icons/benefit-pto.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/benefit-wellbeing.svg b/sites/walmart_careers/static/icons/benefit-wellbeing.svg new file mode 100644 index 00000000..f4982f36 --- /dev/null +++ b/sites/walmart_careers/static/icons/benefit-wellbeing.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/expand.svg b/sites/walmart_careers/static/icons/expand.svg new file mode 100644 index 00000000..3ccbb1bc --- /dev/null +++ b/sites/walmart_careers/static/icons/expand.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/header-mobile-logo.svg b/sites/walmart_careers/static/icons/header-mobile-logo.svg new file mode 100644 index 00000000..47f18701 --- /dev/null +++ b/sites/walmart_careers/static/icons/header-mobile-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/heart-blue.svg b/sites/walmart_careers/static/icons/heart-blue.svg new file mode 100644 index 00000000..f15b2474 --- /dev/null +++ b/sites/walmart_careers/static/icons/heart-blue.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/home-logo.svg b/sites/walmart_careers/static/icons/home-logo.svg new file mode 100644 index 00000000..a080f4fc --- /dev/null +++ b/sites/walmart_careers/static/icons/home-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/sams-club-text.svg b/sites/walmart_careers/static/icons/sams-club-text.svg new file mode 100644 index 00000000..a96751ae --- /dev/null +++ b/sites/walmart_careers/static/icons/sams-club-text.svg @@ -0,0 +1,4 @@ + + + diff --git a/sites/walmart_careers/static/icons/sams-logo.svg b/sites/walmart_careers/static/icons/sams-logo.svg new file mode 100644 index 00000000..a36fca73 --- /dev/null +++ b/sites/walmart_careers/static/icons/sams-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/sams-spark.svg b/sites/walmart_careers/static/icons/sams-spark.svg new file mode 100644 index 00000000..0f3656c0 --- /dev/null +++ b/sites/walmart_careers/static/icons/sams-spark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/sites/walmart_careers/static/icons/search_icon.png b/sites/walmart_careers/static/icons/search_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cc7802687ba7519d266119d2d16aac2774bafb GIT binary patch literal 2414 zcmV-!36b`RP)_adNGwe;0`8^I;%1VxyjEwN?NsIqCnyHMtv=k=McJJ%3(K)nFrU)&sRxw5yiDfp z5`}`XL8rc^CIT%kw9|SJXNEKDX(ZDv-g_Z`=?TrwKMW(5Nu|<2eM!>jVj@RiX=*cz zx1W)D=b5K%ix10$q6^y>A6hkk8+vaYc_hvIuo{~FS__I{jRgxr>>1njazp-;zqBl@ zX72oQMWp)QCg{RDi#gd(@1KD!mwT9DB(9Jj!1a!Z=YKcs;#Z&C44RRnxD zIDUu!|Hx4sklETL;+BuZGF5LywGPh8BT@9i_NSBL1#9T#FSrlxD1t0r9`CNOma&~1 z#QT%KJ#M)z@}%+o8^wG41~JML78~Q9G^MPls>YtHBm9%Lq!8wf6C2^dw!ZbLM+I0| zMJv#v%};C=7Sl6)z2WZ=1=b?Vkz~zrVTt+6l|b$$7PAi0(OywKRQJGWm^6TsoE+_; zg&*JKP4fmaFQr(7dBaZKxQmF@SmJlDdelf(Q^4*zKdqPQ?&uymHd*m)5%;*ny z`Gwx2vwuFNo5d0>uT*0VK3}=ct>N5bn&p8Ock2CjWTvd{@|nVN!*Pm_EIRwgQ@7I* z`(Kx2d|zGk^-oLQV~`@jZqenbv(nZ=x)^Chp+{>}Ni=X?1_Qzxlgq z%<%$*o;rP>0w8iC!%Lq?E6`Hu5(mJ726J~-a~=e5uU#8(&3Xcg15(5@6Ydk`ehLU& zD_3y?Tv%9f6xi3qFZaZIxNUalM%UlqT%24o6qb=O)!qax^6@N&{RV#sc*i+G`x- zI8lH)OM!0I#&byUT&klcF~t9P;rsBg?HznvJr$(>vS++*bx}}YnRtmT*r8uYSZ>97 zkw78_4IH}VFClcn2wJ`4t|{zQwr*6P(DoK32vFIzPH{MC9d^hsB4l@`21HV*sTH;3 ze{9}?RCn|<`I`0l*zsLXcp$JqsEHJM0-`f2_}Y)RmKc@Uq`oPbE35CFnqVEEsa7rJ zMISYoM$Et#q*@rTy)Wcgc@yl3XcX?_j#V_sU7a6-s1i|EEbAiAq{U>%K;X_!SE^-LP_~lpheZ~i(t6N(>8|rU!w`d5 zB2OVe_ozw~Q3Hh?Ge>=e&bMk8%98Hn&j&`qpp_Mq#F~g;;0~ixOEL4=tAQ;^^h{1v z#X(@6I{kp!1|O?8D&s$a12w>fjDCN2^)oC96}F#>dTiGQQHk3J`QWzN`*^&Q7)@Nafd2#^>ALdn8i>SRH&`Mz}9xdO+g%vTV*D6bHi%1rYs^wKxfux&ZbTxL9#Mf9Pla6ia zQy;3e7+N!CO;ZfPU?Bz~RlcQq4>SNI3`*0Ef$5}`;u_Vn(q z|FW?^NW>yzlwypNyNzT*&znBz^rdr$nV2se&w#C{7s=~%VQ2WYHd(saytiF0MTq^v zIXMfqv*}D%4>-<`RA60WcRTK;7nf1Db*%24 z_J~KA_4%TKYx1yU+}o%Iro=K!%R>EFfNQBt^L5>n{18z=yQbLYKzrJp{34f1VZ!pDq6Qvc_8k|q=cU%@|=KAgb6<_L#~RRjOD{k7#j`y#43IB zR`xCCcu!m$mQ%@oEp*)ke4vRP-49qw%O_8@MJTPpD59JBgymHR7s`tG5nW>Z$~wt~ znb9dsb@x#Nt1=Q9jbf>%;zcHko#+LsM=X6P_lpVa40E)t6n~#;@|Zvp({nHb1(Pf= gj{rmE5sPByKXvxh@!z+JkN^Mx07*qoM6N<$f=PITX#fBK literal 0 HcmV?d00001 diff --git a/sites/walmart_careers/static/icons/social-facebook.svg b/sites/walmart_careers/static/icons/social-facebook.svg new file mode 100644 index 00000000..406cd57c --- /dev/null +++ b/sites/walmart_careers/static/icons/social-facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/social-glassdoor.svg b/sites/walmart_careers/static/icons/social-glassdoor.svg new file mode 100644 index 00000000..f16bd4c7 --- /dev/null +++ b/sites/walmart_careers/static/icons/social-glassdoor.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/social-instagram.svg b/sites/walmart_careers/static/icons/social-instagram.svg new file mode 100644 index 00000000..0f11f5c9 --- /dev/null +++ b/sites/walmart_careers/static/icons/social-instagram.svg @@ -0,0 +1,4 @@ + + + + diff --git a/sites/walmart_careers/static/icons/social-linkedin.svg b/sites/walmart_careers/static/icons/social-linkedin.svg new file mode 100644 index 00000000..be19b763 --- /dev/null +++ b/sites/walmart_careers/static/icons/social-linkedin.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/social-x.svg b/sites/walmart_careers/static/icons/social-x.svg new file mode 100644 index 00000000..7ab8ac69 --- /dev/null +++ b/sites/walmart_careers/static/icons/social-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/social-youtube.svg b/sites/walmart_careers/static/icons/social-youtube.svg new file mode 100644 index 00000000..cffa63ed --- /dev/null +++ b/sites/walmart_careers/static/icons/social-youtube.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/spark-white.svg b/sites/walmart_careers/static/icons/spark-white.svg new file mode 100644 index 00000000..a757601d --- /dev/null +++ b/sites/walmart_careers/static/icons/spark-white.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sites/walmart_careers/static/icons/spark-yellow-card.svg b/sites/walmart_careers/static/icons/spark-yellow-card.svg new file mode 100644 index 00000000..4ac91fd6 --- /dev/null +++ b/sites/walmart_careers/static/icons/spark-yellow-card.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sites/walmart_careers/static/icons/spark-yellow.svg b/sites/walmart_careers/static/icons/spark-yellow.svg new file mode 100644 index 00000000..8cfd8cce --- /dev/null +++ b/sites/walmart_careers/static/icons/spark-yellow.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sites/walmart_careers/static/icons/spark.svg b/sites/walmart_careers/static/icons/spark.svg new file mode 100644 index 00000000..8a1f4a3e --- /dev/null +++ b/sites/walmart_careers/static/icons/spark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sites/walmart_careers/static/icons/tile-card.svg b/sites/walmart_careers/static/icons/tile-card.svg new file mode 100644 index 00000000..fff61e75 --- /dev/null +++ b/sites/walmart_careers/static/icons/tile-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/tile-graduation.svg b/sites/walmart_careers/static/icons/tile-graduation.svg new file mode 100644 index 00000000..aaa269a4 --- /dev/null +++ b/sites/walmart_careers/static/icons/tile-graduation.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/tile-growth.svg b/sites/walmart_careers/static/icons/tile-growth.svg new file mode 100644 index 00000000..cdb231e1 --- /dev/null +++ b/sites/walmart_careers/static/icons/tile-growth.svg @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/sites/walmart_careers/static/icons/tile-walmart-plus.svg b/sites/walmart_careers/static/icons/tile-walmart-plus.svg new file mode 100644 index 00000000..8968fb4a --- /dev/null +++ b/sites/walmart_careers/static/icons/tile-walmart-plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/sites/walmart_careers/static/icons/walmart-logo.svg b/sites/walmart_careers/static/icons/walmart-logo.svg new file mode 100644 index 00000000..57324ddd --- /dev/null +++ b/sites/walmart_careers/static/icons/walmart-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sites/walmart_careers/templates/404.html b/sites/walmart_careers/templates/404.html new file mode 100644 index 00000000..4288a424 --- /dev/null +++ b/sites/walmart_careers/templates/404.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Page not found | Walmart Careers{% endblock %} +{% block content %} +
+
+
+

We couldn't find that page

+

The role or page you were looking for isn't here. Try searching for a role instead.

+ Browse open roles + Back to home +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/_job_card.html b/sites/walmart_careers/templates/_job_card.html new file mode 100644 index 00000000..d2361764 --- /dev/null +++ b/sites/walmart_careers/templates/_job_card.html @@ -0,0 +1,32 @@ +{% macro job_card(job, saved_ids) -%} +
+ {{ job.brand }} +
+

{{ job.title }}

+
+
{{ job.store.banner }} #{{ job.store.store_number }}
+
{{ job.store.city }}, {{ job.store.state }}
+
{{ job.store.zip }}
+
+
{{ job.shift_label }} • {{ job.pay_range }}
+
+ View role + {% if job.job_id in saved_ids %} +
+ + + +
+ {% else %} +
+ + + +
+ {% endif %} +
+
+
+{%- endmacro %} diff --git a/sites/walmart_careers/templates/account.html b/sites/walmart_careers/templates/account.html new file mode 100644 index 00000000..6b4d5445 --- /dev/null +++ b/sites/walmart_careers/templates/account.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block title %}My account | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ current_user.display_name }}

+
    +
  • Email{{ current_user.email }}
  • +
  • First name{{ current_user.first_name }}
  • +
  • Last name{{ current_user.last_name }}
  • +
  • Phone number{{ current_user.phone or '—' }}
  • +
  • City{{ current_user.city or '—' }}
  • +
  • State{{ current_user.state or '—' }}
  • +
  • Saved roles{{ saved_count }}
  • +
  • Applications{{ application_count }}
  • +
+ Edit profile + Saved roles + My applications +
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/account_edit.html b/sites/walmart_careers/templates/account_edit.html new file mode 100644 index 00000000..d99299e3 --- /dev/null +++ b/sites/walmart_careers/templates/account_edit.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block title %}Edit profile | Walmart Careers{% endblock %} +{% block content %} +
+
+

Edit your profile

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Cancel +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/applications.html b/sites/walmart_careers/templates/applications.html new file mode 100644 index 00000000..b9db0694 --- /dev/null +++ b/sites/walmart_careers/templates/applications.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% block title %}My applications | Walmart Careers{% endblock %} +{% block content %} +
+
+

My applications ({{ rows|length }})

+ {% if rows %} + + + + + + {% for row in rows %} + + + + + + + {% endfor %} + +
RoleLocationConfirmation numberStatus
{{ row.job.title }}{{ row.job.store.city }}, {{ row.job.store.state }}{{ row.confirmation_no }}{{ row.status }}
+ {% else %} +
+

No applications yet

+

Once you submit an application it shows up here with its confirmation number.

+ Browse open roles +
+ {% endif %} +
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/apply_confirm.html b/sites/walmart_careers/templates/apply_confirm.html new file mode 100644 index 00000000..397ca2d6 --- /dev/null +++ b/sites/walmart_careers/templates/apply_confirm.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% block title %}Review your application | Walmart Careers{% endblock %} +{% block content %} +
+
+

Review your application

+

Step 2 of 2 — check your details, then submit.

+
    +
  • Role{{ job.title }}
  • +
  • Location{{ job.store.banner }} #{{ job.store.store_number }}, {{ job.store.city }}, {{ job.store.state }}
  • +
  • Requisition ID{{ job.job_id }}
  • +
  • Email{{ draft.email }}
  • +
  • Name{{ draft.first_name }} {{ draft.last_name }}
  • +
  • Phone{{ draft.phone }}
  • +
+
+ + + Edit details +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/apply_contact.html b/sites/walmart_careers/templates/apply_contact.html new file mode 100644 index 00000000..d293a45b --- /dev/null +++ b/sites/walmart_careers/templates/apply_contact.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block title %}Apply to {{ job.title }} | Walmart Careers{% endblock %} +{% block content %} +
+
+

Apply: {{ job.title }}

+

{{ job.store.banner }} #{{ job.store.store_number }} — + {{ job.store.city }}, {{ job.store.state }} — Step 1 of 2: contact details

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + Back to the role +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/apply_submitted.html b/sites/walmart_careers/templates/apply_submitted.html new file mode 100644 index 00000000..ed755596 --- /dev/null +++ b/sites/walmart_careers/templates/apply_submitted.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block title %}Application submitted | Walmart Careers{% endblock %} +{% block content %} +
+
+

Application submitted

+

Thanks, {{ application.first_name }}. Your application for {{ job.title }} at + {{ job.store.banner }} #{{ job.store.store_number }} in {{ job.store.city }}, {{ job.store.state }} + has been received.

+
Confirmation number: {{ application.confirmation_no }}
+
    +
  • Status{{ application.status }}
  • +
  • Email{{ application.email }}
  • +
  • Phone{{ application.phone }}
  • +
+ Keep browsing roles + {% if current_user.is_authenticated %} + My applications + {% endif %} +
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/area.html b/sites/walmart_careers/templates/area.html new file mode 100644 index 00000000..7720bb8e --- /dev/null +++ b/sites/walmart_careers/templates/area.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block title %}{{ area.name }} careers | Walmart Careers{% endblock %} +{% block content %} +
+ {{ area.name }} +
+
+

{{ area.name }}

+

{{ area.blurb }}

+ {% if area.is_filterable %} + See all open roles + {% else %} + See all open roles + {% endif %} +
+
+
+ +{% if categories %} +
+
+

Join our team

+
+ {% for category in categories %} + + {{ category.name }} + {{ counts[category.id] }} open + + {% endfor %} +
+
+
+{% else %} +
+
+
+

Programs, not a job family

+

{{ area.name }} hiring runs through every career area on this site. Browse open roles and filter + by the career area, brand, shift or location that fits you.

+ Browse all open roles +
+
+
+{% endif %} + +
+
+

Explore our benefits

+
+ {% for name, blurb, icon in content.BENEFIT_ROWS %} +
+ +
{{ name }}{{ blurb }}
+
+ {% endfor %} +
+
+
+ +
+
+

Our hubs

+
+ {% for hub in hubs %} + {% set copy = content.HUB_COPY.get(hub.store_number) %} + {% if copy %} +
+ {{ copy[0] }} +
+

{{ copy[0] }}

+

{{ copy[1] }}

+ See roles near {{ hub.city }} +
+
+ {% endif %} + {% endfor %} +
+

See all hubs

+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/base.html b/sites/walmart_careers/templates/base.html new file mode 100644 index 00000000..0860655c --- /dev/null +++ b/sites/walmart_careers/templates/base.html @@ -0,0 +1,119 @@ + + + + + + {% block title %}Careers at Walmart{% endblock %} + + + + + + + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+ + + + diff --git a/sites/walmart_careers/templates/hiring_process.html b/sites/walmart_careers/templates/hiring_process.html new file mode 100644 index 00000000..21c60ace --- /dev/null +++ b/sites/walmart_careers/templates/hiring_process.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% from "_job_card.html" import job_card %} +{% block title %}How we hire | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ content.HIRING_HEADING }}

+

{{ content.HIRING_BLURB }}

+
+
+
+
+

Explore something new

+
+ {% for title, blurb in content.HIRING_STEPS %} +

{{ title }}

{{ blurb }}

+ {% endfor %} +
+
+
+
+
+ {% for heading, questions in content.HIRING_FAQ %} +

{{ heading }}

+ {% for question, answer in questions %} +
+ {{ question }} +

{{ answer }}

+
+ {% endfor %} + {% endfor %} +
+
+
+
+

Trending roles

+
+ {% for job in trending %}{{ job_card(job, saved_ids) }}{% endfor %} +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/index.html b/sites/walmart_careers/templates/index.html new file mode 100644 index 00000000..b6d70d96 --- /dev/null +++ b/sites/walmart_careers/templates/index.html @@ -0,0 +1,120 @@ +{% extends "base.html" %} +{% from "_job_card.html" import job_card %} +{% block title %}Careers at Walmart{% endblock %} +{% block content %} +
+
+

{{ content.HERO_HEADLINE_1 }}{{ content.HERO_HEADLINE_2 }}

+ +
+ Walmart associates at work +
+ +
+
+ +
+
+ +
+
+

Trending roles

+
+ {% for job in trending %}{{ job_card(job, saved_ids) }}{% endfor %} +
+

See all open roles

+
+
+ +
+
+

Grow your career here

+
+ {% for area in ribbon_areas %} + {{ area.name }} + {% endfor %} +
+
+
+ +
+
+

Guided by our values

+
+
Associates on the sales floor
+
+

People-led. Tech-powered.

+
    + {% for name, blurb in content.VALUES %} +
  • {{ name }} — {{ blurb }}
  • + {% endfor %} +
+
+
+
+
+ +
+
+

Explore our Benefits

+
+ {% for name, blurb, icon in content.BENEFIT_ROWS %} +
+ +
{{ name }}{{ blurb }}
+
+ {% endfor %} +
+

{{ content.BENEFIT_FOOTNOTE }}

+
+
+ +
+
+

Here, every job is a step toward something greater

+
+ {% for figure, caption in content.STAT_CARDS %} +
{{ figure }}{{ caption }}
+ {% endfor %} +
+
+
+ +
+
+

See our associates in action

+
+ {% for role, kicker, image in content.DAY_IN_THE_LIFE %} +
+ {{ role }} +
{{ kicker }}
{{ role }}
+
+ {% endfor %} +
+
+
+ +
+
+

Find the role that's a perfect fit

+ +
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/job_detail.html b/sites/walmart_careers/templates/job_detail.html new file mode 100644 index 00000000..aff87f98 --- /dev/null +++ b/sites/walmart_careers/templates/job_detail.html @@ -0,0 +1,175 @@ +{% extends "base.html" %} +{% from "_job_card.html" import job_card %} +{% block title %}{{ job.title }} in {{ job.store.city }}, {{ job.store.state }} | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ job.title }}

+
{{ job.store.city }}, {{ job.store.state }}
+
+ Apply now + {% if is_saved %} +
+ + + +
+ {% else %} +
+ + + +
+ {% endif %} +
+
+
+ {% for image in job.hero_images %} + + {% endfor %} +
+
+ +
+
+ + +
+

{{ 'Role summary' if job.population == 'hourly' else 'Position Summary...' }}

+

{{ job.summary }}

+ +

What you'll do...

+ {% for paragraph in job.description.split('\n\n') %} +

{{ paragraph }}

+ {% endfor %} + + {% if job.population == 'hourly' %} +

What you'll bring

+
    + {% for bullet in job.additional_description %}
  • {{ bullet }}
  • {% endfor %} +
+ {% if job.hashtag %}

{{ job.hashtag }}

{% endif %} + + {% else %} +

Minimum Qualifications...

+

{{ content.MIN_QUAL_PREAMBLE }}

+
    + {% for option in job.min_qualifications %}
  • {{ option }}
  • {% endfor %} +
+

Preferred Qualifications...

+

{{ content.PREF_QUAL_PREAMBLE }}

+

{{ job.preferred_qualifications }}

+

Primary Location...

+

{{ job.store.street }}, {{ job.store.city }}, {{ job.store.state }} {{ job.store.zip }}, + United States of America

+ {% endif %} + +
+ + +
+
+ +
+
+

Benefits you'll enjoy

+
+ {% for title, kicker, body, icon in benefit_tiles %} +
+ + {{ title }} + {{ kicker }} +

{{ body }}

+
+ {% endfor %} +
+
+ {% for name, blurb, icon in content.JOB_BENEFIT_ROWS %} +
+ +
{{ name }}{{ blurb }}
+
+ {% endfor %} +
+

Learn more

+
+
+ +
+
+

{{ content.LIFE_AT_WALMART_HEADING }}

+
+
+ {% for paragraph in content.LIFE_AT_WALMART %}

{{ paragraph }}

{% endfor %} +
+
Walmart associates
+
+
{{ content.LIFE_AT_WALMART_QUOTE }}
+
+
+ +{% if related %} +
+
+

Related roles

+
+ {% for other in related %}{{ job_card(other, saved_ids) }}{% endfor %} +
+
+
+{% endif %} +{% endblock %} diff --git a/sites/walmart_careers/templates/locations.html b/sites/walmart_careers/templates/locations.html new file mode 100644 index 00000000..c6a8eaf7 --- /dev/null +++ b/sites/walmart_careers/templates/locations.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Our locations | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ content.LOCATIONS_HEADING }}

+

{{ content.LOCATIONS_BLURB }}

+
+
+
+
+

Hubs around the world

+
+ {% for hub in hubs %} + {% set copy = content.HUB_COPY.get(hub.store_number) %} +
+ {{ copy[0] if copy else hub.city }} +
+

{{ copy[0] if copy else hub.city }}

+

{{ copy[1] if copy else 'A Walmart hub location.' }}

+

{{ hub.location_name }} — {{ hub.street }}, {{ hub.city }}, + {{ hub.state }} {{ hub.zip }} — {{ counts[hub.id] }} open roles

+ See roles near {{ hub.city }} +
+
+ {% endfor %} +
+

{{ content.LOCATIONS_CLOSING }}

+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/login.html b/sites/walmart_careers/templates/login.html new file mode 100644 index 00000000..89306d30 --- /dev/null +++ b/sites/walmart_careers/templates/login.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block title %}Sign in | Walmart Careers{% endblock %} +{% block content %} +
+
+

Sign in to your candidate account

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ + {% if next_url %}{% endif %} +
+ + +
+
+ + +
+ +
+

New here? Create a candidate account.

+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/register.html b/sites/walmart_careers/templates/register.html new file mode 100644 index 00000000..a9adde46 --- /dev/null +++ b/sites/walmart_careers/templates/register.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block title %}Create an account | Walmart Careers{% endblock %} +{% block content %} +
+
+

Create your candidate account

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ + {% if next_url %}{% endif %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

Already have an account? Sign in.

+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/results.html b/sites/walmart_careers/templates/results.html new file mode 100644 index 00000000..0e70be7e --- /dev/null +++ b/sites/walmart_careers/templates/results.html @@ -0,0 +1,172 @@ +{% extends "base.html" %} +{% from "_job_card.html" import job_card %} +{% block title %}{{ total }} open roles{% if filters.q %} for "{{ filters.q }}"{% endif %} | Walmart Careers{% endblock %} +{% block content %} +
+
+ + +
+ + + {% if filters.tab == 'future' %} +
+

No future roles here

+

{{ content.EMPTY_FUTURE_ROLES }}

+ Back to open roles +
+ {% elif filters.tab == 'content' %} +
+

No content results

+

{{ content.EMPTY_CONTENT_TAB }}

+ Back to open roles +
+ {% else %} +
+

{{ "{:,}".format(total) }} open role{{ '' if total == 1 else 's' }}{% if filters.q %} for “{{ filters.q }}”{% endif %}

+
+ + + {% if jobs %} +
+ {% for job in jobs %}{{ job_card(job, saved_ids) }}{% endfor %} +
+ {% if pages > 1 %} + + {% endif %} + {% else %} +
+

No roles matched

+

Try a different keyword, widen your location radius, or clear a filter.

+ Reset all filters +
+ {% endif %} + {% endif %} +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/saved_roles.html b/sites/walmart_careers/templates/saved_roles.html new file mode 100644 index 00000000..00617d8a --- /dev/null +++ b/sites/walmart_careers/templates/saved_roles.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% from "_job_card.html" import job_card %} +{% block title %}Saved roles | Walmart Careers{% endblock %} +{% block content %} +
+
+

Saved roles ({{ rows|length }})

+ {% if not current_user.is_authenticated %} +
+

Sign in to see your saved roles

+

Saving a role keeps it on this page so you can come back and apply later.

+ Sign in + Create an account +
+ {% elif not rows %} +
+

You haven't saved any roles yet

+

Use the Save role button on any posting to keep it here.

+ Browse open roles +
+ {% else %} +
+ {% for row in rows %}{{ job_card(row.job, saved_ids) }}{% endfor %} +
+ {% endif %} +
+
+
+
+

Trending roles

+
+ {% for job in trending %}{{ job_card(job, saved_ids) }}{% endfor %} +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/terms.html b/sites/walmart_careers/templates/terms.html new file mode 100644 index 00000000..76bfad42 --- /dev/null +++ b/sites/walmart_careers/templates/terms.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Terms & Conditions | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ content.TERMS_HEADING }}

+ {% for heading, body in content.TERMS_SECTIONS %} +

{{ heading }}

+

{{ body }}

+ {% endfor %} + +
+
+{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 73323233..9516e975 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -1,11 +1,12 @@ #!/bin/bash -# WebSyn startup: launch all 17 mirror sites, then exec the original CMD. +# WebSyn startup: launch all 18 mirror sites, then exec the original CMD. # This preserves the base image's browser env server (port 8100) as PID 1. set -e SITES=(allrecipes amazon apple arxiv bbc_news booking github google_flights google_map google_search huggingface wolfram_alpha - cambridge_dictionary coursera espn merriam_webster ikea) + cambridge_dictionary coursera espn merriam_webster ikea + walmart_careers) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" @@ -17,7 +18,7 @@ for d in "${SITES[@]}"; do cp -a "/opt/WebSyn/$d/instance_seed" "/opt/WebSyn/$d/instance" done -echo "[WebSyn] Starting 17 sites on ports ${BASE_PORT}-$((BASE_PORT + 16))..." +echo "[WebSyn] Starting 18 sites on ports ${BASE_PORT}-$((BASE_PORT + 17))..." for i in "${!SITES[@]}"; do site="${SITES[$i]}" port=$((BASE_PORT + i)) @@ -51,8 +52,8 @@ except Exception: exit(1) ready=$((ready + 1)) fi done - echo " [${elapsed}/${max_wait}s] ${ready}/17 sites ready" - if [ $ready -eq 17 ]; then + echo " [${elapsed}/${max_wait}s] ${ready}/18 sites ready" + if [ $ready -eq 18 ]; then break fi done @@ -78,6 +79,6 @@ done echo "[WebSyn] Starting control server on :8101 (PID 1)..." # Control server becomes PID 1 — receives SIGTERM on `docker stop`, -# keeps the container alive as long as it's running. The 17 site +# keeps the container alive as long as it's running. The 18 site # subprocesses are managed via /tmp/websyn_pids/.pid. exec python3 /opt/control_server.py --port 8101 From 12cfb03f11aec219290bf5bbbc68d1b737373434 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sat, 5 Sep 2026 23:38:05 -0500 Subject: [PATCH 02/15] docs: bump site count to 18 Adds Walmart Careers to the mirror list and moves every published port range from 40000-40016 to 40000-40017 (41000-41017 for the alt-port test container). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K1NUPpd54bZirE5uzkBgK3 --- AGENTS.md | 12 ++++++------ CLAUDE.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9d6868df..22be9948 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ A coding agent (Claude Code, Cursor, Aider, Codex, ...) is reading this. Read on ## What it is -17 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image. +18 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image. Two repos: - **code** (this one) — Flask apps, control plane, scripts. @@ -48,17 +48,17 @@ Inside the image, sites live at `/opt/WebSyn//`. The path predates the ren # fresh clone ./scripts/fetch_assets.sh # pulls assets from HF ./scripts/build.sh # docker build -t webharbor:dev . -docker run -d -p 8101:8101 -p 40000-40016:40000-40016 webharbor:dev +docker run -d -p 8101:8101 -p 40000-40017:40000-40017 webharbor:dev ``` Or use the published image directly: ```bash -docker run -d -p 8101:8101 -p 40000-40016:40000-40016 \ +docker run -d -p 8101:8101 -p 40000-40017:40000-40017 \ battalion7244/webharbor:latest ``` -Sites are on `40000`-`40016` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane: +Sites are on `40000`-`40017` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane: | Method | Path | Purpose | |--------|---------------------|-------------------------------------------| @@ -136,13 +136,13 @@ python3 -m py_compile sites//app.py # 3. run on alt ports (don't collide with anything you already have running) docker run -d --rm --name wh-test \ - -p 8201:8101 -p 41000-41016:40000-40016 webharbor:dev + -p 8201:8101 -p 41000-41017:40000-40017 webharbor:dev # 4. control plane healthy, all sites alive curl -s http://localhost:8201/health | python3 -m json.tool | head # 5. every site renders 200 -for p in $(seq 41000 41016); do +for p in $(seq 41000 41017); do curl -so /dev/null -w "$p:%{http_code}\n" http://localhost:$p/ done diff --git a/CLAUDE.md b/CLAUDE.md index 9e2a0c1d..028f2c1e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,4 +16,4 @@ The full agent guide is loaded above via `@AGENTS.md`. The notes below apply onl ## Existing containers -If a container is already running on `:8101` / `:40000-40016`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41016`). +If a container is already running on `:8101` / `:40000-40017`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41017`). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a251c56..58f90f49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ git clone https://github.com//webharbor && cd webharbor ./scripts/fetch_assets.sh # pull current assets ./scripts/new_site.py mywebsite # OR edit an existing site ./scripts/build.sh && docker run -d --rm \ - -p 8101:8101 -p 40000-40016:40000-40016 webharbor:dev + -p 8101:8101 -p 40000-40017:40000-40017 webharbor:dev # iterate locally... ./scripts/extract_assets.sh ../webharbor-static-pr/ # split assets out diff --git a/README.md b/README.md index aa2b31ea..a6eca2a1 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ WebHarbor takes a different approach. We leverage coding agent (e.g., Claude Cod One command to run all web environments: ```bash -docker run -p 8101:8101 -p 40000-40016:40000-40016 battalion7244/webharbor:latest +docker run -p 8101:8101 -p 40000-40017:40000-40017 battalion7244/webharbor:latest ``` -Then point your agent at `http://localhost:40000` through `http://localhost:40016` to explore 17 local mirrors of webvoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, and IKEA`. +Then point your agent at `http://localhost:40000` through `http://localhost:40017` to explore 18 local mirrors of webvoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, and Walmart Careers`. For sub-second reset between rollouts, expose the control plane and call `/reset/`: From 3d23719cf38f36a39aecf6434d82d2ebb28789fd Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sat, 5 Sep 2026 23:39:57 -0500 Subject: [PATCH 03/15] feat(walmart_careers): add 20 benchmark tasks Contributor-side task definitions only: web_name, id, ques, web, upstream_url. No verifier_path, no judge_rubric, no answer key anywhere in the repo. Coverage: keyword search and detail lookup (0-3), multi-constraint filtering including brand/shift/rate/pay, location radius and career-area + employment type (4-7), cross-page comparison (8-10), stateful save, unsave, apply, register and profile edit (11-15), and five hard multi-hop tasks that need a career-area page, a category, filters and two or more detail pages before an answer exists (10, 16-19). Login tasks carry the demo credentials in ques. Every answer is a detail-only field (requisition ID paired with another detail-only value, street address, open positions, shift start window, qualification text, worker-type chip, hashtag, confirmation number) or a database change. No task depends on the current date, a posted date, a tab count, or knowledge available without opening the site. Verified: image rebuilds, :41017 returns 200, /reset/walmart_careers returns ready:true and leaves md5(instance) == md5(instance_seed). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K1NUPpd54bZirE5uzkBgK3 --- sites/walmart_careers/tasks.jsonl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 sites/walmart_careers/tasks.jsonl diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl new file mode 100644 index 00000000..0359cedf --- /dev/null +++ b/sites/walmart_careers/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name": "Walmart Careers", "id": "Walmart Careers--0", "ques": "Search for Optician roles and open the posting at the Neighborhood Market in Wichita, KS. Report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=optician"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--6", "ques": "Set your location to Cleveland, OH within 25 miles, filter to Full time roles on a Weekday Day shift, and open the Online Order Filling Team Supervisor posting. Report the street address and the number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and its pay range.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--8", "ques": "There are Auto Care Center Technician postings at two Mississippi stores. Open both and report which store number has more open positions and how many.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=auto+care+center+technician"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--9", "ques": "Two Freight Handler postings are located in Marcy, NY at different facilities. Which one has the earlier shift start time? Report its requisition ID and that start window.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=freight+handler"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--10", "ques": "Compare the Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery) postings in Bentonville, AR and Hoboken, NJ. Which one requires more years of experience under \"Option 2\" of its Minimum Qualifications? Report that posting's requisition ID and the number of years.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2435546"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--11", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), search for Yard Driver roles and save the Williamsburg, VA posting to your Saved roles.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--19", "ques": "Log in as bob.c@test.com (password: TestPass123!). From the Stores and Clubs career area page, open the Digital Pickup and Delivery category, filter it to Full time roles, and open the posting with the fewest open positions. Save that role to your Saved roles, then report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/stores-and-clubs"} From 573a4a7db36f45405af936387cb970a6de19e68f Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:04:19 -0500 Subject: [PATCH 04/15] feat(walmart_careers): match the live header, results and job-detail chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 (evolve-env). Rebuilt the page chrome against scraped_data/reference/*.png and extended the mirror where a task could not be driven through the real UI. Header: the full spark + <> + "Careers" lockup, then Career areas (a dropdown listing all six areas), Brands, Resources, About Us and Military; a white search pill with a blue circular button; and a user-icon popover holding My account / Saved roles / Login-Signup / EN (initials avatar plus My applications / Log out once signed in). Nothing wraps at 1440px and the bar spans the full viewport. Results: heading is "N open roles" with no query echo, the count badge sits on the Open roles tab only, "Add your location" and "Filters" open popovers instead of permanently expanded sidebar panels, and "Sort by: Relevance" is a dropdown. The left column is now the cluster map alone. Active facets render as removable chips under the toolbar so filter state stays readable with the popover closed. Cards are population-aware — salaried postings drop the "banner #store" line — and both buttons are styled as the live outlined "Select +" pill. Job detail: two layouts branched on job.population, both matching their reference capture — three-photo masthead with the identity card (solid ld-blue for salaried, blue-over-navy for hourly), the left Role Details rail (sub-items for hourly only), the address block with a map card, and three dark navy chips. "Apply now" is ld-blue on both. Also in this phase: - new GET /about-us page behind the header's About Us item - the location box accepts a whole state or territory ("Puerto Rico", "PR"), which selects every posting in that state and ignores the radius; cities and ZIPs keep the haversine radius. Task 16 needs a reliable route to all five PR postings. - related_jobs() excludes the current store in its fallback branch too, so "Related roles" can no longer surface another posting at the same location - the results cluster map keeps its geographic aspect ratio; the detail page map card is a small SVG with roads and a pin - tasks 3 and 7 now answer with detail-only fields (3 pairs the hashtag with the open-position count, 7 swaps the card-visible pay range for the street address); task 15's upstream_url points at /us/en/candidate-home _content.py also carries the Phase 4 trending-role retarget (same file): the home page no longer links straight to the targets of tasks 1, 10 and 14. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Uso3TzMAGoE9CanJMW6wcJ --- sites/walmart_careers/_content.py | 77 +++- sites/walmart_careers/app.py | 220 ++++++++--- sites/walmart_careers/static/css/site.css | 347 +++++++++++++----- sites/walmart_careers/tasks.jsonl | 6 +- .../walmart_careers/templates/_job_card.html | 19 +- sites/walmart_careers/templates/about.html | 49 +++ sites/walmart_careers/templates/base.html | 83 +++-- sites/walmart_careers/templates/index.html | 21 +- .../walmart_careers/templates/job_detail.html | 257 ++++++++----- sites/walmart_careers/templates/results.html | 239 +++++++----- 10 files changed, 947 insertions(+), 371 deletions(-) create mode 100644 sites/walmart_careers/templates/about.html diff --git a/sites/walmart_careers/_content.py b/sites/walmart_careers/_content.py index 171a07ad..d22401ff 100644 --- a/sites/walmart_careers/_content.py +++ b/sites/walmart_careers/_content.py @@ -15,11 +15,13 @@ SITE_NAME = "Walmart Careers" COPYRIGHT = "©2026 Walmart Inc." -# Job ids surfaced as "Trending roles" on the home page and the hiring page. +# Job ids surfaced as "Trending roles" on the home page, the hiring page and the +# logged-out saved-roles page. None of them is the target of a benchmark task — +# a trending card would otherwise hand an agent the target without a search. TRENDING_JOB_IDS = [ - "R-2463275", - "R-2451180", - "CP-9046-11101", + "R-2414279", + "R-2413636", + "CP-1236-10888", ] HERO_HEADLINE_1 = "Cashiers wanted." @@ -355,3 +357,70 @@ def benefit_tiles_for(brand: str, population: str) -> list[tuple[str, str, str, PR_OUTLINE = [ (-67.3, 18.5), (-66.4, 18.5), (-65.6, 18.4), (-65.6, 17.9), (-66.6, 17.9), (-67.3, 18.1), ] + + +# --------------------------------------------------------------------------- # +# Header navigation. The dropdown groups mirror the live top bar: +# Career areas | Brands | Resources | About Us | Military. +# --------------------------------------------------------------------------- # +# (label, brand filter value) for the Brands dropdown. +NAV_BRANDS = [ + ("Walmart", "Walmart"), + ("Sam's Club", "Sam's Club"), + ("VIZIO", "Vizio"), +] +# (label, endpoint) for the Resources dropdown. +NAV_RESOURCES = [ + ("How we hire", "resources_hiring"), + ("Office Locations", "resources_location"), + ("Terms & Conditions", "resources_terms"), +] + +ABOUT_HEADING = "About Us" +ABOUT_BLURB = ( + "Walmart is a people-led, tech-powered omnichannel retailer. Around the world our associates " + "serve customers in stores, clubs, distribution centers and online, and every one of those jobs " + "is a step toward something greater." +) +ABOUT_SECTIONS = [ + ( + "Our purpose", + "We save people money so they can live better. That purpose has guided every decision since " + "Sam Walton opened the first store in Rogers, Arkansas, and it still shapes how we hire, how " + "we promote, and how we invest in the communities we serve.", + ), + ( + "How we work", + "We are people-led and tech-powered. Associates in stores, clubs, supply chain and the home " + "office work with the same tools and the same data, so a good idea can start anywhere and " + "reach millions of customers quickly.", + ), + ( + "Where you can grow", + "About three quarters of our salaried store managers began as hourly associates. Live Better U " + "pays for tuition, books and fees, and Walmart Academy runs skills training in every market we " + "operate in.", + ), +] + +# --------------------------------------------------------------------------- # +# US state / territory names, used to resolve a plain state in the location box +# ("PR", "Puerto Rico", "Ohio") into a state-wide result set. +# --------------------------------------------------------------------------- # +STATE_NAMES = { + "AL": "Alabama", "AK": "Alaska", "AZ": "Arizona", "AR": "Arkansas", + "CA": "California", "CO": "Colorado", "CT": "Connecticut", "DC": "District of Columbia", + "DE": "Delaware", "FL": "Florida", "GA": "Georgia", "HI": "Hawaii", + "IA": "Iowa", "ID": "Idaho", "IL": "Illinois", "IN": "Indiana", + "KS": "Kansas", "KY": "Kentucky", "LA": "Louisiana", "MA": "Massachusetts", + "MD": "Maryland", "ME": "Maine", "MI": "Michigan", "MN": "Minnesota", + "MO": "Missouri", "MS": "Mississippi", "MT": "Montana", "NC": "North Carolina", + "ND": "North Dakota", "NE": "Nebraska", "NH": "New Hampshire", "NJ": "New Jersey", + "NM": "New Mexico", "NV": "Nevada", "NY": "New York", "OH": "Ohio", + "OK": "Oklahoma", "OR": "Oregon", "PA": "Pennsylvania", "PR": "Puerto Rico", + "RI": "Rhode Island", "SC": "South Carolina", "SD": "South Dakota", "TN": "Tennessee", + "TX": "Texas", "UT": "Utah", "VA": "Virginia", "VI": "U.S. Virgin Islands", + "VT": "Vermont", "WA": "Washington", "WI": "Wisconsin", "WV": "West Virginia", + "WY": "Wyoming", +} +STATE_CODES_BY_NAME = {name.lower(): code for code, name in sorted(STATE_NAMES.items())} diff --git a/sites/walmart_careers/app.py b/sites/walmart_careers/app.py index 904ac891..a1bd1426 100644 --- a/sites/walmart_careers/app.py +++ b/sites/walmart_careers/app.py @@ -102,6 +102,14 @@ class Area(db.Model): def url(self) -> str: return url_for("career_area", slug=self.slug) + @property + def nav_url(self) -> str: + """Where the "Career areas" menu points: the area page when there is one, + otherwise straight to that area's open roles.""" + if self.has_index_page: + return url_for("career_area", slug=self.slug) + return url_for("results", area=self.slug) + class Category(db.Model): __tablename__ = "categories" @@ -317,31 +325,60 @@ def safe_next(raw: str | None) -> str | None: return raw -def resolve_location(raw: str) -> Store | None: - """Resolve free text against seeded stores: 'City, ST', 'City', 'ST' or a ZIP prefix.""" +def resolve_location(raw: str) -> dict | None: + """Resolve free text typed into the location box. + + Returns ``None`` when nothing matches, otherwise a scope dict: + + * ``{"kind": "state", "state": "PR", "label": "Puerto Rico", "store": }`` + when the text names a whole state or territory — the result set is then + every role in that state, with no radius applied. + * ``{"kind": "store", "store": , "label": "Cleveland, OH"}`` when the + text names a city, a "City, ST" pair or a ZIP — the radius then applies. + """ text = (raw or "").strip() if not text: return None stores = Store.query.order_by(Store.store_number).all() + + def state_scope(code: str) -> dict | None: + anchor = next((s for s in stores if s.state == code), None) + if anchor is None: + return None + return { + "kind": "state", + "state": code, + "label": content.STATE_NAMES.get(code, code), + "store": anchor, + } + + # Whole-state searches: "PR", "Puerto Rico", "Ohio". + upper = text.upper() + if len(upper) == 2 and upper in content.STATE_NAMES: + scope = state_scope(upper) + if scope: + return scope + code = content.STATE_CODES_BY_NAME.get(text.lower()) + if code: + scope = state_scope(code) + if scope: + return scope + digits = re.sub(r"[^0-9]", "", text) if len(digits) >= 5: for store in stores: if store.zip.replace("-", "").startswith(digits[:5]): - return store + return {"kind": "store", "store": store, "label": store.city_state} parts = [p.strip() for p in text.split(",") if p.strip()] city = parts[0].lower() if parts else "" state = parts[1].upper()[:2] if len(parts) > 1 else "" if state: for store in stores: if store.city.lower() == city and store.state == state: - return store + return {"kind": "store", "store": store, "label": store.city_state} for store in stores: if store.city.lower() == city: - return store - if len(text) == 2: - for store in stores: - if store.state == text.upper(): - return store + return {"kind": "store", "store": store, "label": store.city_state} return None @@ -441,14 +478,14 @@ def score_job(job: Job, tokens: list[str]) -> float: return score -def search_jobs(filters: dict) -> tuple[list[Job], Store | None, bool]: - """Return (ordered jobs, resolved location store, location_failed).""" +def search_jobs(filters: dict) -> tuple[list[Job], dict | None, bool]: + """Return (ordered jobs, resolved location scope, location_failed).""" jobs = Job.query.order_by(Job.job_id).all() - location_store = None + location = None location_failed = False if filters["loc"]: - location_store = resolve_location(filters["loc"]) - location_failed = location_store is None + location = resolve_location(filters["loc"]) + location_failed = location is None if filters["brand"]: jobs = [j for j in jobs if j.brand in filters["brand"]] @@ -473,12 +510,16 @@ def search_jobs(filters: dict) -> tuple[list[Job], Store | None, bool]: j for j in jobs if j.category and (j.category.slug.lower() in wanted_cats or j.category.name.lower() in wanted_cats) ] - if location_store is not None: - radius = filters["radius"] - jobs = [ - j for j in jobs - if haversine_miles(location_store.lat, location_store.lng, j.store.lat, j.store.lng) <= radius - ] + if location is not None: + if location["kind"] == "state": + jobs = [j for j in jobs if j.store.state == location["state"]] + else: + anchor = location["store"] + radius = filters["radius"] + jobs = [ + j for j in jobs + if haversine_miles(anchor.lat, anchor.lng, j.store.lat, j.store.lng) <= radius + ] tokens = tokenize(filters["q"]) if tokens: @@ -494,18 +535,25 @@ def search_jobs(filters: dict) -> tuple[list[Job], Store | None, bool]: jobs.sort(key=lambda j: (-j.posted_date.toordinal(), j.sort_rank)) else: jobs.sort(key=lambda j: (j.sort_rank, j.job_id)) - return jobs, location_store, location_failed + return jobs, location, location_failed + +def cluster_map_svg(jobs: list[Job], width: int = 520, height: int = 620) -> str: + """Deterministic server-rendered cluster map (no third-party map tiles). -def cluster_map_svg(jobs: list[Job], width: int = 520, height: int = 380) -> str: - """Deterministic server-rendered cluster map (no third-party map tiles).""" + Equirectangular with a cos(mean latitude) correction so the outline keeps a + believable shape, then centred vertically in the panel. + """ lon_min, lon_max = -125.0, -65.0 lat_min, lat_max = 17.0, 50.0 pad = 12 + scale = (width - 2 * pad) / (lon_max - lon_min) + lat_scale = scale / math.cos(math.radians((lat_min + lat_max) / 2)) + y_offset = (height - (lat_max - lat_min) * lat_scale) / 2 def project(lat: float, lng: float) -> tuple[float, float]: - x = pad + (lng - lon_min) / (lon_max - lon_min) * (width - 2 * pad) - y = pad + (lat_max - lat) / (lat_max - lat_min) * (height - 2 * pad) + x = pad + (lng - lon_min) * scale + y = y_offset + (lat_max - lat) * lat_scale return round(x, 1), round(y, 1) def path_for(points: list[tuple[float, float]]) -> str: @@ -530,9 +578,9 @@ def path_for(points: list[tuple[float, float]]) -> str: f'', - f'', - f'', - f'', + f'', + f'', + f'', ] for x, y, radius, count, label in bubbles: parts.append( @@ -550,24 +598,48 @@ def path_for(points: list[tuple[float, float]]) -> str: return "".join(parts) -def pin_card_svg(store: Store, width: int = 300, height: int = 170) -> str: - """Small deterministic SVG pin card used on the job detail page.""" - return ( +def pin_card_svg(store: Store, width: int = 490, height: int = 230) -> str: + """Small deterministic SVG map card used beside the address on the detail page. + + Stands in for the Google Maps thumbnail on the live page: same palette, a road + grid seeded from the store's own coordinates, and a pin over the location. + """ + seed = int(abs(store.lat * 1000) + abs(store.lng * 1000)) % 97 + vx = 40 + (seed % 7) * 22 + vy = 60 + (seed % 5) * 18 + parts = [ f'' - f'' - f'' - f'' - f'' - f'' - f'' + f'role="img" aria-label="Map of {store.city}, {store.state}" ' + f'xmlns="http://www.w3.org/2000/svg">', + f'', + # water + f'', + # roads + f'', + f'', + f'', + f'', + ] + px, py = width / 2, height / 2 - 18 + parts.append( + f'' + f'' + f'' f'' - f'{store.city}, {store.state}' - f'{store.zip}' - f'' ) + parts.append( + f'' + f'{store.city}' + ) + parts.append( + f'' + f'Map data ©2026 Walmart Careers mirror' + ) + parts.append("") + return "".join(parts) def trending_jobs() -> list[Job]: @@ -591,6 +663,7 @@ def related_jobs(job: Job, limit: int = 3) -> list[Job]: Job.query.filter( Job.area_id == job.area_id, Job.job_id != job.job_id, + Job.store_id != job.store_id, ~Job.job_id.in_([r.job_id for r in rows]), ) .order_by(Job.sort_rank, Job.job_id) @@ -612,13 +685,13 @@ def saved_job_ids() -> set[str]: @app.context_processor def inject_globals(): - areas = ( - Area.query.filter_by(has_index_page=True) - .order_by(Area.display_order) - .all() - ) return { - "nav_areas": areas, + # the six career areas listed in the header "Career areas" menu + "nav_areas": ( + Area.query.filter_by(is_filterable=True) + .order_by(Area.display_order) + .all() + ), "content": content, "current_year": content.MIRROR_REFERENCE_DATE.year, "search_q": (request.args.get("q") or request.args.get("searchQuery") or ""), @@ -644,10 +717,45 @@ def index(): ) +FACET_KEYS = ("area", "category", "brand", "shift", "type", "rate") + + +def active_filter_count(filters: dict) -> int: + """How many facet selections are active — the number on the Filters button.""" + return sum(len(filters[key]) for key in FACET_KEYS) + + +def active_filter_chips(filters: dict) -> list[dict]: + """One removable chip per active selection, so the current filter state stays + readable without leaving the Filters popover hanging open over the results.""" + names = {a.slug: a.name for a in Area.query.all()} + names.update({c.slug: c.name for c in Category.query.all()}) + chips: list[dict] = [] + for key in FACET_KEYS: + for value in filters[key]: + remaining = [v for v in filters[key] if v != value] + chips.append( + { + "label": names.get(value, value), + "remove": url_for("results") + + "?" + + filters_query(filters, page=1, **{key: remaining}), + } + ) + if filters["loc"]: + chips.append( + { + "label": f"{filters['loc']} · within {filters['radius']} miles", + "remove": url_for("results") + "?" + filters_query(filters, loc="", page=1), + } + ) + return chips + + @app.route("/results") def results(): filters = current_filters() - jobs, location_store, location_failed = search_jobs(filters) + jobs, location, location_failed = search_jobs(filters) total = len(jobs) pages = max(1, math.ceil(total / PAGE_SIZE)) page = min(filters["page"], pages) @@ -669,11 +777,13 @@ def results(): employment_type_values=EMPLOYMENT_TYPE_VALUES, rate_values=RATE_VALUES, radius_values=RADIUS_VALUES, - location_store=location_store, + location=location, location_failed=location_failed, map_svg=cluster_map_svg(jobs), saved_ids=saved_job_ids(), qs=filters_query, + filter_count=active_filter_count(filters), + filter_chips=active_filter_chips(filters), ) @@ -851,6 +961,14 @@ def resources_terms(): return render_template("terms.html") +@app.route("/about-us") +def about_us(): + return render_template( + "about.html", + areas=Area.query.filter_by(is_filterable=True).order_by(Area.display_order).all(), + ) + + @app.route("/login", methods=["GET", "POST"]) def login(): next_url = safe_next(request.args.get("next")) diff --git a/sites/walmart_careers/static/css/site.css b/sites/walmart_careers/static/css/site.css index d2800a77..fdeda27a 100644 --- a/sites/walmart_careers/static/css/site.css +++ b/sites/walmart_careers/static/css/site.css @@ -50,49 +50,80 @@ img { max-width: 100%; } .wrap { max-width: 1360px; margin: 0 auto; padding: 0 32px; } .wrap-narrow { max-width: 900px; margin: 0 auto; padding: 0 32px; } +.wrap-bleed { max-width: none; padding: 0 24px; } /* ------------------------------ header ---------------------------------- */ .site-header { background: var(--ld-blue-100); min-height: var(--header-h); display: flex; align-items: center; - position: sticky; top: 0; z-index: 50; + position: sticky; top: 0; z-index: 60; } -.site-header .wrap { display: flex; align-items: center; gap: 24px; width: 100%; } -.brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; text-decoration: none; } -.brand img { height: 34px; width: auto; } -.brand span { color: #fff; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; } -.main-nav { display: flex; gap: 22px; flex: 1 1 auto; } -.main-nav a { - color: #fff; text-decoration: none; font-size: 16px; padding: 8px 2px; - border-bottom: 2px solid transparent; +.site-header .wrap { + display: flex; align-items: center; gap: 20px; width: 100%; flex-wrap: nowrap; + max-width: none; padding: 0 24px; +} +.brand { display: flex; align-items: center; flex: 0 0 auto; text-decoration: none; } +.brand img { height: 38px; width: auto; } + +.main-nav { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; white-space: nowrap; } +.main-nav .nav-link, .nav-menu > summary { + color: #fff; text-decoration: none; font-size: 16px; padding: 10px 12px; + border-radius: 8px; display: inline-flex; align-items: center; gap: 6px; + cursor: pointer; list-style: none; white-space: nowrap; +} +.nav-menu > summary::-webkit-details-marker { display: none; } +.nav-menu > summary::marker { content: ""; } +.main-nav .nav-link:hover, .nav-menu > summary:hover, +.nav-menu[open] > summary { background: var(--ld-blue-130); color: #fff; } + +.nav-menu { position: relative; } +.nav-pop { + position: absolute; top: calc(100% + 10px); left: 0; z-index: 70; + background: #fff; border-radius: 16px; padding: 12px 8px; min-width: 268px; + box-shadow: 0 8px 28px rgba(0, 30, 96, .22); display: flex; flex-direction: column; +} +.nav-pop a, .nav-pop .linkish { + color: var(--ld-blue-160); text-decoration: none; padding: 9px 16px; border-radius: 10px; + font-size: 15px; text-align: left; white-space: nowrap; +} +.nav-pop a:hover, .nav-pop .linkish:hover { background: var(--ld-blue-9); color: var(--ld-blue-160); } +.nav-pop hr { border: 0; border-top: 1px solid var(--ld-gray-20); margin: 8px 12px; width: calc(100% - 24px); } +.nav-pop .lang { padding: 9px 16px; font-size: 14px; color: var(--ld-text-subtle); } + +.header-search { display: flex; align-items: center; flex: 1 1 auto; justify-content: flex-end; } +.header-search form { + display: flex; align-items: center; background: #fff; + border-radius: 999px; padding: 5px 5px 5px 22px; + width: 100%; max-width: 448px; } -.main-nav a:hover, .main-nav a.active { border-bottom-color: var(--ld-spark-100); color: #fff; } - -.header-search { display: flex; align-items: center; flex: 0 0 auto; } -.header-search form { display: flex; align-items: center; background: #fff; border-radius: 999px; padding: 4px 4px 4px 18px; } .header-search input { - border: 0; outline: none; width: 240px; font-size: 15px; font-family: inherit; + border: 0; outline: none; flex: 1; min-width: 0; font-size: 15px; font-family: inherit; color: var(--ld-blue-160); background: transparent; } +.header-search input::placeholder { color: var(--ld-blue-160); } .header-search button { - border: 0; background: var(--ld-blue-100); color: #fff; width: 40px; height: 40px; + border: 0; background: var(--ld-blue-100); width: 38px; height: 38px; border-radius: 999px; cursor: pointer; display: grid; place-items: center; } -.header-search button img { width: 18px; height: 18px; filter: brightness(0) invert(1); } +.header-search button svg { color: #fff; } -.user-menu { flex: 0 0 auto; } -.user-links { display: flex; gap: 16px; align-items: center; } -.user-links a { color: #fff; text-decoration: none; font-size: 15px; } -.user-links a:hover { text-decoration: underline; color: #fff; } -.user-links form { margin: 0; } +.user-menu { flex: 0 0 auto; position: relative; } +.user-menu > summary { + list-style: none; cursor: pointer; color: #fff; display: grid; place-items: center; + width: 40px; height: 40px; border-radius: 999px; +} +.user-menu > summary::-webkit-details-marker { display: none; } +.user-menu > summary::marker { content: ""; } +.user-menu[open] > summary, .user-menu > summary:hover { background: var(--ld-blue-130); } +.user-menu .nav-pop { left: auto; right: 0; } .avatar { width: 34px; height: 34px; border-radius: 999px; background: var(--ld-spark-100); color: var(--ld-blue-160); display: grid; place-items: center; font-weight: 700; font-size: 14px; } .linkish { - background: none; border: 0; color: #fff; font: inherit; cursor: pointer; - text-decoration: underline; padding: 0; + background: none; border: 0; font: inherit; cursor: pointer; padding: 0; + color: var(--ld-blue-160); } /* ------------------------------ buttons --------------------------------- */ @@ -109,14 +140,39 @@ img { max-width: 100%; } .btn-sm { padding: 7px 18px; font-size: 14px; } /* ------------------------------ hero ------------------------------------ */ -.hero { background: var(--ld-blue-100); color: #fff; padding: 0; } -.hero .inner { padding: 64px 0 72px; } -.hero h1 { font-size: 56px; line-height: 1.05; font-weight: 400; margin: 0 0 32px; max-width: 720px; } +.hero { background: var(--ld-blue-100); color: #fff; padding: 0; position: relative; } +.hero .inner { padding: 96px 0 168px; text-align: center; } +.hero h1 { font-size: 62px; line-height: 1.08; font-weight: 400; margin: 0 auto 42px; max-width: 900px; } .hero h1 span { display: block; } -.hero-search { display: flex; background: #fff; border-radius: 999px; padding: 8px 8px 8px 28px; max-width: 720px; } -.hero-search input { flex: 1; border: 0; outline: none; font-size: 18px; font-family: inherit; color: var(--ld-blue-160); } -.hero-search button { border: 0; background: var(--ld-blue-100); color: #fff; border-radius: 999px; padding: 14px 34px; font: inherit; font-weight: 600; cursor: pointer; } -.hero-photo { display: block; width: 100%; height: 320px; object-fit: cover; } +.hero-search { + display: flex; align-items: center; background: var(--ld-blue-130); + border-radius: 999px; padding: 10px 10px 10px 34px; max-width: 720px; margin: 0 auto; +} +.hero-search input { + flex: 1; min-width: 0; border: 0; outline: none; font-size: 19px; font-family: inherit; + color: #fff; background: transparent; +} +.hero-search input::placeholder { color: #fff; } +.hero-search button { + border: 0; background: #fff; color: var(--ld-blue-100); border-radius: 999px; + width: 54px; height: 54px; display: grid; place-items: center; cursor: pointer; +} +.hero-strip { + position: absolute; left: 0; right: 0; bottom: -104px; + display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 24px; padding: 0 24px; align-items: end; +} +.hero-strip > img, .hero-strip-mid img { + display: block; width: 100%; height: 250px; object-fit: cover; border-radius: 20px; +} +.hero-strip > img:first-child { border-radius: 0 20px 20px 0; margin-left: -24px; } +.hero-strip > img:last-child { border-radius: 20px 0 0 20px; margin-right: -24px; } +.hero-strip-mid { position: relative; } +.hero-pill { + position: absolute; left: 50%; transform: translateX(-50%); bottom: 24px; + background: var(--ld-blue-100); color: #fff; border-color: var(--ld-blue-100); +} +.hero-pill:hover { background: var(--ld-blue-130); color: #fff; } +.hero + section { padding-top: 152px; } /* ------------------------------ sections -------------------------------- */ section { padding: 56px 0; } @@ -167,91 +223,202 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } .bento .values li { margin-bottom: 10px; } /* ------------------------------ job cards ------------------------------- */ -.job-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } +.job-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; } .job-grid.one-col { grid-template-columns: 1fr; } +.job-grid.three-col { grid-template-columns: repeat(3, minmax(0, 1fr)); } .job-card { - border: 1px solid var(--ld-gray-20); border-radius: 32px; padding: 24px; - background: #fff; display: flex; gap: 16px; align-items: flex-start; + border: 1px solid var(--ld-gray-20); border-radius: 24px; padding: 22px 24px 24px; + background: #fff; display: block; } .job-card:hover { border-color: var(--ld-blue-130); } -.job-card .spark { width: 25px; height: 25px; flex: 0 0 25px; margin-top: 4px; } -.job-card .body { flex: 1; min-width: 0; } -.job-card h3 { margin: 0 0 6px; font-size: 18px; font-weight: 700; } +.job-card .spark { width: 24px; height: 24px; display: block; margin-bottom: 14px; } +.job-card h3 { margin: 0 0 10px; font-size: 17px; font-weight: 700; } .job-card h3 a { color: var(--ld-blue-160); text-decoration: none; } .job-card h3 a:hover { text-decoration: underline; } -.job-card .meta { font-size: 16px; color: var(--ld-blue-160); } +.job-card .meta { font-size: 15px; color: var(--ld-blue-160); } .job-card .meta div { margin-bottom: 2px; } -.job-card .pay { margin-top: 8px; font-size: 16px; } -.job-card .actions { margin-top: 14px; display: flex; gap: 10px; align-items: center; } +.job-card .pay { margin-top: 6px; font-size: 15px; } +.job-card .actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; } +.job-card .actions form { margin: 0; } + +/* the live "Select +" pill */ +.pill { + display: inline-flex; align-items: center; gap: 10px; border-radius: 999px; + border: 1px solid var(--ld-blue-160); background: #fff; color: var(--ld-blue-160); + font: inherit; font-size: 14px; font-weight: 700; padding: 8px 18px; + text-decoration: none; cursor: pointer; +} +.pill span { font-weight: 400; font-size: 16px; } +.pill:hover { background: var(--ld-blue-9); color: var(--ld-blue-160); } +.pill-on { background: var(--ld-blue-160); color: #fff; } +.pill-on:hover { background: var(--ld-blue-130); color: #fff; } /* ------------------------------ results --------------------------------- */ -.results-layout { display: grid; grid-template-columns: 340px 1fr; gap: 32px; padding: 32px 0 64px; } -.results-aside .panel { background: var(--ld-gray-5); border-radius: 24px; padding: 20px; margin-bottom: 20px; } -.results-aside .panel h3 { font-size: 18px; } -.cluster-map { display: block; border-radius: 16px; } +.results-layout { display: grid; grid-template-columns: 372px 1fr; gap: 28px; padding: 28px 0 64px; } +.results-aside .map-panel { border-radius: 20px; overflow: hidden; position: sticky; top: 100px; } +.cluster-map { display: block; } .pin-card { display: block; border-radius: 16px; } -.filter-panel fieldset { border: 0; padding: 0; margin: 0 0 18px; } -.filter-panel legend { font-weight: 700; padding: 0 0 8px; font-size: 16px; } -.filter-panel label { display: flex; gap: 8px; align-items: center; font-size: 15px; padding: 3px 0; cursor: pointer; } -.filter-panel .cat-group { margin: 4px 0 10px 8px; } -.filter-panel .cat-group summary { cursor: pointer; font-size: 15px; padding: 3px 0; } -.filter-actions { display: flex; gap: 10px; margin-top: 12px; } - -.results-head { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; flex-wrap: wrap; } -.results-head h1 { font-size: 32px; font-weight: 300; margin: 0; } -.tabs { display: flex; gap: 28px; border-bottom: 1px solid var(--ld-gray-20); margin: 16px 0 24px; } +.tabs { display: flex; gap: 32px; border-bottom: 1px solid var(--ld-gray-20); margin: 0 0 22px; } .tabs a { - text-decoration: none; color: var(--ld-text-subtle); padding: 10px 2px; - border-bottom: 3px solid transparent; font-weight: 600; + text-decoration: none; color: var(--ld-text-subtle); padding: 10px 2px 14px; + border-bottom: 3px solid transparent; font-weight: 600; display: inline-flex; + align-items: center; gap: 8px; +} +.tabs a.active { color: var(--ld-blue-160); border-bottom-color: var(--ld-blue-100); } +.tab-badge { + background: var(--ld-blue-100); color: #fff; border-radius: 999px; + padding: 2px 10px; font-size: 12px; font-weight: 700; +} + +.results-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; } +.results-head h1 { font-size: 34px; font-weight: 300; margin: 0; } +.loc-note { color: var(--ld-text-subtle); font-size: 15px; margin: 10px 0 0; } + +/* popovers: Location, Filters and Sort by */ +.pop-wrap { position: relative; } +.pop-wrap > summary { list-style: none; cursor: pointer; } +.pop-wrap > summary::-webkit-details-marker { display: none; } +.pop-wrap > summary::marker { content: ""; } +.loc-link { + display: inline-flex; align-items: center; gap: 8px; color: var(--ld-blue-100); + font-size: 15px; padding: 8px 2px; +} +.loc-link:hover { text-decoration: underline; } +.tool-btn { + display: inline-flex; align-items: center; gap: 8px; color: var(--ld-blue-160); + font-size: 15px; padding: 8px 10px; border-radius: 8px; +} +.tool-btn:hover { background: var(--ld-gray-5); } +.tool-btn .caret { font-size: 12px; } +.count-badge { + background: var(--ld-blue-100); color: #fff; border-radius: 999px; min-width: 20px; + height: 20px; display: inline-grid; place-items: center; font-size: 12px; font-weight: 700; + padding: 0 6px; } -.tabs a.active { color: var(--ld-blue-100); border-bottom-color: var(--ld-blue-100); } -.sort-row { display: flex; gap: 16px; align-items: center; margin-bottom: 20px; font-size: 15px; } +.toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 18px; margin: 12px 0 16px; } +.active-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 0 0 20px; } +.active-chip { + display: inline-flex; align-items: center; gap: 8px; border-radius: 999px; + border: 1px solid var(--ld-gray-20); background: var(--ld-blue-9); + color: var(--ld-blue-160); text-decoration: none; padding: 6px 14px; font-size: 14px; +} +.active-chip:hover { border-color: var(--ld-blue-130); color: var(--ld-blue-160); } +.clear-all { font-size: 14px; color: var(--ld-blue-160); text-decoration: underline; } + +.pop-panel { + position: absolute; right: 0; top: calc(100% + 8px); z-index: 40; background: #fff; + border-radius: 20px; box-shadow: 0 10px 34px rgba(0, 30, 96, .22); padding: 22px 24px; + min-width: 340px; text-align: left; +} +.pop-panel.wide { min-width: 700px; } +.pop-head h2 { font-size: 20px; font-weight: 400; margin: 0 0 16px; } +.pop-panel .field { margin-bottom: 14px; } +.pop-panel .radio-row { + display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 15px; + color: var(--ld-blue-160); text-decoration: none; cursor: pointer; +} +.pop-panel .radio-row:hover { color: var(--ld-blue-100); } +.pop-actions { + display: flex; justify-content: flex-end; align-items: center; gap: 18px; + border-top: 1px solid var(--ld-gray-20); margin-top: 16px; padding-top: 16px; +} +.pop-actions .reset { color: var(--ld-blue-160); text-decoration: underline; font-size: 15px; } + +.filter-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 22px; } +.filter-grid fieldset { border: 0; padding: 0; margin: 0; min-width: 0; } +.filter-grid legend { font-weight: 700; padding: 0 0 8px; font-size: 16px; } +.filter-grid label { display: flex; gap: 8px; align-items: flex-start; font-size: 14px; padding: 4px 0; cursor: pointer; } +.filter-grid .cat-group { margin: 2px 0 10px 20px; } +.filter-grid .cat-group summary { cursor: pointer; font-size: 13px; padding: 3px 0; color: var(--ld-text-subtle); } +.area-col { max-height: 420px; overflow-y: auto; } + .empty-panel { background: var(--ld-blue-9); border-radius: 24px; padding: 32px; } -.pagination { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 32px; align-items: center; } +.pagination { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 32px; align-items: center; justify-content: center; } .pagination a, .pagination span { min-width: 40px; height: 40px; border-radius: 999px; display: grid; place-items: center; - text-decoration: none; border: 1px solid var(--ld-gray-20); color: var(--ld-blue-160); padding: 0 12px; + text-decoration: none; border: 1px solid transparent; color: var(--ld-blue-160); padding: 0 12px; } -.pagination .current { background: var(--ld-blue-100); color: #fff; border-color: var(--ld-blue-100); } +.pagination a:hover { background: var(--ld-gray-5); } +.pagination .current { border-color: var(--ld-blue-100); color: var(--ld-blue-100); font-weight: 700; } /* ------------------------------ job detail ------------------------------ */ -.job-hero { background: var(--ld-blue-100); color: #fff; padding: 40px 0 0; } -.job-hero h1 { font-size: 40px; font-weight: 400; margin: 0 0 8px; } -.job-hero .loc { font-size: 18px; margin-bottom: 20px; } -.job-hero .hero-actions { display: flex; gap: 14px; align-items: center; margin-bottom: 28px; } -.job-hero .photos { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } -.job-hero .photos img { display: block; width: 100%; height: 190px; object-fit: cover; } - -.detail-layout { display: grid; grid-template-columns: 230px 1fr 330px; gap: 36px; padding: 36px 0 64px; } -.detail-nav { position: sticky; top: 100px; align-self: start; } +.detail-top { padding: 22px 0 0; } +.hero-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px; align-items: start; } +.hero-col { display: flex; flex-direction: column; gap: 24px; } +.spark-pill { + background: var(--ld-blue-100); border-radius: 999px; height: 74px; + display: grid; place-items: center; +} +.spark-pill img { height: 34px; width: auto; } +.hero-photo-tall { display: block; width: 100%; height: 424px; object-fit: cover; border-radius: 20px; } +.hero-photo-wide { display: block; width: 100%; height: 298px; object-fit: cover; border-radius: 20px; } + +.id-card { border-radius: 20px; overflow: hidden; color: #fff; } +.id-card .id-top { background: var(--ld-blue-100); padding: 14px 0 10px; text-align: center; } +.id-card .grip { + display: block; width: 42px; height: 5px; border-radius: 999px; + background: rgba(255, 255, 255, .9); margin: 0 auto 14px; +} +.id-card .id-spark { height: 30px; width: auto; } +.id-card .id-body { padding: 12px 20px 18px; text-align: center; } +.id-card .id-title { font-size: 18px; } +.id-card .id-loc { font-size: 14px; font-weight: 700; margin-top: 8px; } +.id-salaried .id-body { background: var(--ld-blue-100); } +.id-hourly .id-body { background: var(--ld-blue-160); } +.id-card .id-actions { + background: #fff; display: flex; align-items: center; gap: 8px; padding: 12px 16px; + border: 1px solid var(--ld-gray-20); border-top: 0; + border-radius: 0 0 20px 20px; +} +.id-card .id-actions form { margin: 0; } +.icon-btn { + background: none; border: 0; cursor: pointer; color: var(--ld-blue-160); + width: 36px; height: 36px; border-radius: 999px; display: grid; place-items: center; padding: 0; +} +.icon-btn:hover { background: var(--ld-gray-200); color: var(--ld-blue-160); } +.apply-now { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; } +.apply-now span { font-weight: 400; } + +.detail-layout { display: grid; grid-template-columns: 260px 1fr; gap: 48px; padding: 40px 0 64px; } +.detail-nav { + position: sticky; top: 100px; align-self: start; background: var(--ld-blue-9); + border-radius: 0 24px 24px 0; padding: 26px 24px; margin-left: -24px; +} .detail-nav ul { list-style: none; margin: 0; padding: 0; } -.detail-nav li { padding: 6px 0; } +.detail-nav li { padding: 7px 0; } .detail-nav a { text-decoration: none; color: var(--ld-blue-160); } -.detail-nav .sub { padding-left: 16px; font-size: 15px; } - -.fact-card { border: 1px solid var(--ld-gray-20); border-radius: 24px; padding: 22px; position: sticky; top: 100px; } -.fact-card h2 { font-size: 20px; margin: 0 0 12px; } -.fact-card address { font-style: normal; line-height: 1.5; margin-bottom: 12px; } -.fact-card .positions { - display: inline-block; background: var(--ld-blue-10); border-radius: 999px; - padding: 6px 14px; font-size: 14px; font-weight: 600; margin-bottom: 10px; +.detail-nav li:first-child a { font-weight: 700; } +.detail-nav .sub { padding-left: 16px; } + +.detail-main h1 { font-size: 44px; font-weight: 300; margin: 0 0 30px; line-height: 1.1; } +.fact-row { display: grid; grid-template-columns: 1fr 490px; gap: 32px; align-items: start; } +.fact-col .banner-line { font-weight: 700; font-size: 20px; margin-bottom: 4px; } +.fact-col address { font-style: normal; line-height: 1.55; margin: 0 0 16px; } +.positions { + display: inline-block; background: var(--ld-blue-9); border-radius: 6px; + padding: 4px 10px; font-size: 13px; color: var(--ld-blue-160); } -.fact-card .req-id { font-size: 14px; color: var(--ld-text-subtle); margin-bottom: 14px; } -.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 8px; } +.fact-col .req-id { font-size: 14px; color: var(--ld-blue-100); margin-top: 12px; } +.map-col { border-radius: 16px; overflow: hidden; } + +.chips { display: grid; grid-template-columns: 1fr 1fr 2fr; gap: 20px; margin: 28px 0 8px; } .chip { background: var(--ld-blue-160); color: #fff; border-radius: 999px; - padding: 8px 16px; font-size: 14px; font-weight: 600; + padding: 16px 26px; font-size: 15px; display: flex; flex-direction: column; + align-items: flex-start; gap: 12px; min-height: 96px; justify-content: center; } -.footnote { font-size: 13px; color: var(--ld-text-subtle); } +.chip svg { color: #fff; } +.footnote { font-size: 13px; color: var(--ld-blue-160); margin-top: 14px; } -.detail-body h2 { font-size: 26px; font-weight: 400; margin: 32px 0 12px; } +.detail-body { margin-top: 36px; } +.detail-body h2 { font-size: 30px; font-weight: 300; margin: 36px 0 14px; } .detail-body h2:first-child { margin-top: 0; } .detail-body p { margin: 0 0 14px; } .detail-body ul { margin: 0 0 16px; padding-left: 20px; } .detail-body li { margin-bottom: 8px; } -.hashtag { font-weight: 700; color: var(--ld-blue-100); } +.hashtag { font-weight: 400; color: var(--ld-blue-100); } .legal { font-size: 13px; color: var(--ld-text-subtle); margin-top: 20px; } .benefit-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } @@ -337,7 +504,15 @@ table.data th { font-size: 14px; text-transform: uppercase; letter-spacing: .04e @media (max-width: 1100px) { .results-layout, .detail-layout { grid-template-columns: 1fr; } .carousel, .stat-grid, .tile-grid, .steps, .footer-cols { grid-template-columns: repeat(2, 1fr); } - .job-grid, .category-grid, .benefit-tiles, .hub-grid, .bento { grid-template-columns: 1fr; } - .detail-nav, .fact-card { position: static; } + .job-grid, .job-grid.three-col, .category-grid, .benefit-tiles, .hub-grid, .bento { grid-template-columns: 1fr; } + .hero-strip { position: static; grid-template-columns: 1fr; padding: 0; } + .hero .inner { padding: 56px 0; } + .hero + section { padding-top: 56px; } + .hero-grid { grid-template-columns: 1fr; } + .fact-row, .chips { grid-template-columns: 1fr; } + .filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .pop-panel, .pop-panel.wide { min-width: 300px; } + .detail-nav, .fact-card, .results-aside .map-panel { position: static; } + .detail-nav { margin-left: 0; border-radius: 24px; } .hero h1 { font-size: 40px; } } diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl index 0359cedf..78850a98 100644 --- a/sites/walmart_careers/tasks.jsonl +++ b/sites/walmart_careers/tasks.jsonl @@ -1,11 +1,11 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--0", "ques": "Search for Optician roles and open the posting at the Neighborhood Market in Wichita, KS. Report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=optician"} {"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} {"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section, and how many open positions does the posting list?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} {"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} {"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} {"web_name": "Walmart Careers", "id": "Walmart Careers--6", "ques": "Set your location to Cleveland, OH within 25 miles, filter to Full time roles on a Weekday Day shift, and open the Online Order Filling Team Supervisor posting. Report the street address and the number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and its pay range.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and the street address listed for its location.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} {"web_name": "Walmart Careers", "id": "Walmart Careers--8", "ques": "There are Auto Care Center Technician postings at two Mississippi stores. Open both and report which store number has more open positions and how many.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=auto+care+center+technician"} {"web_name": "Walmart Careers", "id": "Walmart Careers--9", "ques": "Two Freight Handler postings are located in Marcy, NY at different facilities. Which one has the earlier shift start time? Report its requisition ID and that start window.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=freight+handler"} {"web_name": "Walmart Careers", "id": "Walmart Careers--10", "ques": "Compare the Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery) postings in Bentonville, AR and Hoboken, NJ. Which one requires more years of experience under \"Option 2\" of its Minimum Qualifications? Report that posting's requisition ID and the number of years.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2435546"} @@ -13,7 +13,7 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} {"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} {"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} {"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} diff --git a/sites/walmart_careers/templates/_job_card.html b/sites/walmart_careers/templates/_job_card.html index d2361764..6eed4475 100644 --- a/sites/walmart_careers/templates/_job_card.html +++ b/sites/walmart_careers/templates/_job_card.html @@ -1,3 +1,9 @@ +{# + Result card. The lines mirror the live site: salaried postings sit at an office + and show only "City, ST zip", hourly postings show the "banner #store" line above + it. Everything else (street, requisition ID, open positions, shift window, + qualifications) is detail-page-only. +#} {% macro job_card(job, saved_ids) -%}

{{ job.title }}

-
{{ job.store.banner }} #{{ job.store.store_number }}
-
{{ job.store.city }}, {{ job.store.state }}
-
{{ job.store.zip }}
+ {% if not job.store.is_office %} +
{{ job.store.banner }} #{{ job.store.store_number }}
+ {% endif %} +
{{ job.store.city }}, {{ job.store.state }}  {{ job.store.zip }}
{{ job.shift_label }} • {{ job.pay_range }}
- View role + View role + {% if job.job_id in saved_ids %}
- +
{% else %}
- +
{% endif %}
diff --git a/sites/walmart_careers/templates/about.html b/sites/walmart_careers/templates/about.html new file mode 100644 index 00000000..b7e3a44b --- /dev/null +++ b/sites/walmart_careers/templates/about.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% block title %}About Us | Walmart Careers{% endblock %} +{% block content %} +
+
+

{{ content.ABOUT_HEADING }}

+

{{ content.ABOUT_BLURB }}

+
+
+ +
+
+
+ {% for heading, body in content.ABOUT_SECTIONS %} +
+

{{ heading }}

+

{{ body }}

+
+ {% endfor %} +
+
+
+ +
+
+

{{ content.LIFE_AT_WALMART_HEADING }}

+
+
+ {% for paragraph in content.LIFE_AT_WALMART %}

{{ paragraph }}

{% endfor %} +
    + {% for title, blurb in content.VALUES %}
  • {{ title }} — {{ blurb }}
  • {% endfor %} +
+
+
+ Walmart associates +
+
+
+
+ +
+
+

Explore our career areas

+
+ {% for area in areas %}{{ area.name }}{% endfor %} +
+
+
+{% endblock %} diff --git a/sites/walmart_careers/templates/base.html b/sites/walmart_careers/templates/base.html index 0860655c..0d6194c6 100644 --- a/sites/walmart_careers/templates/base.html +++ b/sites/walmart_careers/templates/base.html @@ -11,43 +11,81 @@ @@ -78,22 +116,23 @@

Careers

Brands

    - {% for brand in content.FOOTER_BRANDS %} -
  • {{ brand }}
  • + {% for label, value in content.NAV_BRANDS %} +
  • {{ label }}
  • {% endfor %}

Resources

About

    +
  • About Us
  • Military
  • All open roles
  • Candidate home
  • diff --git a/sites/walmart_careers/templates/index.html b/sites/walmart_careers/templates/index.html index b6d70d96..1aed0814 100644 --- a/sites/walmart_careers/templates/index.html +++ b/sites/walmart_careers/templates/index.html @@ -7,10 +7,23 @@

    {{ content.HERO_HEADLINE_1 }}{{ content.HERO_HEADLINE_2 }}

- Walmart associates at work +
+ Associates in a home office space + + Associates in a distribution center +
@@ -30,10 +43,10 @@

{{ content.HERO_HEADLINE_1 }}{{ content.HERO_HEADLINE_2 }

-
+

Trending roles

-
+
{% for job in trending %}{{ job_card(job, saved_ids) }}{% endfor %}

See all open roles

diff --git a/sites/walmart_careers/templates/job_detail.html b/sites/walmart_careers/templates/job_detail.html index aff87f98..e5dad1f5 100644 --- a/sites/walmart_careers/templates/job_detail.html +++ b/sites/walmart_careers/templates/job_detail.html @@ -2,125 +2,194 @@ {% from "_job_card.html" import job_card %} {% block title %}{{ job.title }} in {{ job.store.city }}, {{ job.store.state }} | Walmart Careers{% endblock %} {% block content %} -
-
-

{{ job.title }}

-
{{ job.store.city }}, {{ job.store.state }}
-
- Apply now - {% if is_saved %} -
- - - -
- {% else %} -
- - - -
+{% set salaried = job.population == 'salaried' %} + +{# + Both populations use the live three-photo masthead; the identity card in the + middle column is solid ld-blue for salaried postings and blue-over-navy for + hourly ones, exactly as reference/job_detail_corp.png vs reference/job_detail.png. +#} +
+
+
+
+
+ Walmart spark +
+ {% if job.hero_images|length > 0 %} + + {% endif %} +
+
+ {% if job.hero_images|length > 1 %} + + {% endif %} +
+
+ + +
+
+
{{ job.title }}
+
{{ job.store.city }}, {{ job.store.state }}
+
+
+ + + + {% if is_saved %} +
+ + + +
+ {% else %} +
+ + + +
+ {% endif %} + Apply now + +
+
+
+ {% if job.hero_images|length > 2 %} + {% endif %}
-
- {% for image in job.hero_images %} - - {% endfor %} -
-
+
-
-

{{ 'Role summary' if job.population == 'hourly' else 'Position Summary...' }}

-

{{ job.summary }}

+
+

{{ job.title }}

-

What you'll do...

- {% for paragraph in job.description.split('\n\n') %} -

{{ paragraph }}

- {% endfor %} - - {% if job.population == 'hourly' %} -

What you'll bring

-
    - {% for bullet in job.additional_description %}
  • {{ bullet }}
  • {% endfor %} -
- {% if job.hashtag %}

{{ job.hashtag }}

{% endif %} - - +
diff --git a/sites/walmart_careers/templates/results.html b/sites/walmart_careers/templates/results.html index 0e70be7e..b4ac9287 100644 --- a/sites/walmart_careers/templates/results.html +++ b/sites/walmart_careers/templates/results.html @@ -1,107 +1,20 @@ {% extends "base.html" %} {% from "_job_card.html" import job_card %} -{% block title %}{{ total }} open roles{% if filters.q %} for "{{ filters.q }}"{% endif %} | Walmart Careers{% endblock %} +{% block title %}{{ total }} open roles | Walmart Careers{% endblock %} {% block content %}
-
+
Open roles + href="{{ url_for('results') }}?{{ qs(filters, tab='jobs') }}">Open roles + {{ "{:,}".format(total) }} Future roles No content results Back to open roles
{% else %} +
-

{{ "{:,}".format(total) }} open role{{ '' if total == 1 else 's' }}{% if filters.q %} for “{{ filters.q }}”{% endif %}

+

{{ "{:,}".format(total) }} open role{{ '' if total == 1 else 's' }}

+
+ + + Add your location + +
+

Location

+ {% if filters.q %}{% endif %} + {% for key in ['area','category','brand','shift','type','rate'] %} + {% for value in filters[key] %}{% endfor %} + {% endfor %} +
+ + +
+ {% for radius in radius_values %} + + {% endfor %} +

A state or territory name (for example Puerto Rico or + PR) returns every role in that state and ignores the radius.

+
+ Reset + +
+
+
-
- Sort by: - - {% if filters.sort == 'relevance' %}●{% else %}○{% endif %} Relevance - - {% if filters.sort == 'most_recent' %}●{% else %}○{% endif %} Most recent + + {% if location_failed %} +

We couldn't find that location.

+ {% elif location %} +

+ {% if location.kind == 'state' %} + Showing roles in {{ location.label }} ({{ location.state }}). + {% else %} + Showing roles within {{ filters.radius }} miles of {{ location.label }}. + {% endif %} +

+ {% endif %} + +
+
+ + {% if filter_count %}{{ filter_count }}{% endif %} + + Filters + +
+

Filters

+ {% if filters.q %}{% endif %} + {% if filters.loc %} + + + {% endif %} +
+
+ Brand + {% for value in brand_values %} + + {% endfor %} +
+
+ Shift + {% for value in shift_values %} + + {% endfor %} +
+
+ Employment Type + {% for value in employment_type_values %} + + {% endfor %} + Rate + {% for value in rate_values %} + + {% endfor %} +
+
+ Career Area + {% for area in areas %} + +
+ Categories in {{ area.name }} + {% for category in area.categories %} + + {% endfor %} +
+ {% endfor %} +
+
+
+ Reset + +
+
+
+ +
+ Sort by: {{ 'Relevance' if filters.sort == 'relevance' else 'Most recent' }} + + +
+ {% if filter_chips %} +
+ {% for chip in filter_chips %} + {{ chip.label }} × + {% endfor %} + Clear all +
+ {% endif %} + {% if jobs %}
{% for job in jobs %}{{ job_card(job, saved_ids) }}{% endfor %} From 5fab41dab5ce1ddc0df6d6cc7b8e9ecb5864acbd Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:04:38 -0500 Subject: [PATCH 05/15] fix(walmart_careers): harden all 20 tasks against leaks and shortcuts Phase 4 (harden-env). Audited every task on the four dimensions and the 13 leak archetypes against the running container's rendered HTML; the task x page matrix is in sites/walmart_careers/VERIFICATION.md. De-leaking: - hashtags are now unique per title family (37 values, e.g. #pharmacytechjobs, #opticianjobs) instead of one shared value per career area, so task 3's answer no longer identifies six different families - the logged-in saved-roles page no longer renders trending roles alongside the saved list task 17 has to read; logged out it still shows them, as upstream does _assert_distractors() now covers all 20 tasks instead of 8: - a TASK_TARGETS table asserts each task's locator (title + city, or title + store) resolves to exactly one posting, and to the expected requisition id - "full match" means "satisfies every constraint the task states", not "shares the target's title"; every task's result set is checked for >= 6 rows with <= 50% full matches - new per-task blocks for tasks 0-3, 6, 7, 11-15, 18 and 19, plus global checks that hashtags and salaried qualification texts are unique and that no trending role is a task target - the relevance check now covers tasks 0, 2 and 13: no target may be rank 1 for its own natural query Catalog fixes the new assertions caught: - the Optician family's placements were reordered so the task 0 target is not the first result for q=optician (this reassigns the four Optician requisition ids; the target is now CP-5991-11240) - task 4's target is the Plano TX Merchandising and Stocking Associate, not the Freezer/Cooler Associate at the same club: both are Sam's Club Part time Weekend Overnight postings and only one is at or under $20/hr The freezer still builds byte-identically across consecutive runs. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Uso3TzMAGoE9CanJMW6wcJ --- sites/walmart_careers/catalog_source.py | 74 ++-- sites/walmart_careers/seed_data.py | 344 ++++++++++++++---- .../templates/saved_roles.html | 5 +- 3 files changed, 315 insertions(+), 108 deletions(-) diff --git a/sites/walmart_careers/catalog_source.py b/sites/walmart_careers/catalog_source.py index 715626a0..dbb9e0bc 100644 --- a/sites/walmart_careers/catalog_source.py +++ b/sites/walmart_careers/catalog_source.py @@ -225,7 +225,7 @@ "title": "Freight Handler", "area": "supply-chain-and-transportation", "category": "SC&T Operations", - "hashtag": "#supplychainjobs", + "hashtag": "#freighthandlerjobs", "summary": "Career opportunities in Freight Handling roles include Receiving, Unloading, " "Processing, Orderfilling and Shipping.", "do": [ @@ -258,7 +258,7 @@ "title": "eCom Warehouse Worker", "area": "supply-chain-and-transportation", "category": "SC&T Operations", - "hashtag": "#supplychainjobs", + "hashtag": "#ecomwarehousejobs", "summary": "Pick, pack and ship the online orders that customers are waiting on, inside one of " "our fulfillment buildings.", "do": [ @@ -285,7 +285,7 @@ "title": "Order Filler", "area": "supply-chain-and-transportation", "category": "SC&T Operations", - "hashtag": "#supplychainjobs", + "hashtag": "#orderfillerjobs", "summary": "Build store-ready pallets from the pick line and stage them for the outbound fleet.", "do": [ "Order Fillers at {banner} #{store} in {city}, {state} select cases from the pick line, build " @@ -311,7 +311,7 @@ "title": "Yard Driver-Off Property", "area": "supply-chain-and-transportation", "category": "Drivers", - "hashtag": "#supplychainjobs", + "hashtag": "#yarddriverjobs", "summary": "Move trailers between the yard, the dock doors and nearby off-property lots.", "do": [ "Yard Drivers at {banner} #{store} in {city}, {state} shuttle trailers between dock doors, the " @@ -362,7 +362,7 @@ "title": "Asset Protection Associate - All DC/FC", "area": "supply-chain-and-transportation", "category": "Security and Asset Protection", - "hashtag": "#supplychainjobs", + "hashtag": "#dcassetprotectionjobs", "summary": "Protect people, product and property inside a distribution or fulfillment building.", "do": [ "Asset Protection Associates at {banner} #{store} in {city}, {state} control access at the guard " @@ -389,7 +389,7 @@ "title": "Facility Maintenance Technician", "area": "supply-chain-and-transportation", "category": "Engineering", - "hashtag": "#supplychainjobs", + "hashtag": "#facilitymaintenancejobs", "summary": "Keep conveyors, dock equipment and building systems running across the shift.", "do": [ "Facility Maintenance Technicians at {banner} #{store} in {city}, {state} perform preventive " @@ -416,7 +416,7 @@ "title": "Automation Technician", "area": "supply-chain-and-transportation", "category": "Engineering", - "hashtag": "#supplychainjobs", + "hashtag": "#automationtechjobs", "summary": "Support the robotics and controls that run our automated storage and retrieval systems.", "do": [ "Automation Technicians at {banner} #{store} in {city}, {state} maintain the robotics cells, " @@ -441,7 +441,7 @@ "title": "Aviation Line Service Technician", "area": "supply-chain-and-transportation", "category": "Aviation", - "hashtag": "#supplychainjobs", + "hashtag": "#aviationjobs", "summary": "Fuel, tow and service company aircraft on the ramp at a fleet operations base.", "do": [ "Line Service Technicians supporting {banner} #{store} in {city}, {state} marshal, fuel, tow and " @@ -466,7 +466,7 @@ "title": "Inventory Control Clerk", "area": "supply-chain-and-transportation", "category": "SC&T Operations", - "hashtag": "#supplychainjobs", + "hashtag": "#inventorycontroljobs", "summary": "Own cycle counts, research and the paperwork that keeps building inventory accurate.", "do": [ "Inventory Control Clerks at {banner} #{store} in {city}, {state} run daily cycle counts, " @@ -493,7 +493,7 @@ "title": "Cashier & Front End Services", "area": "stores-and-clubs", "category": "Cashier and Front-End Services", - "hashtag": "#storejobs", + "hashtag": "#frontendservicesjobs", "summary": "Greet members and customers at the front end, ring transactions and keep lines moving.", "do": [ "At {banner} #{store} in {city}, {state} you are the last person a customer sees, so you set the " @@ -521,7 +521,7 @@ "title": "Cosmetics Cashier", "area": "stores-and-clubs", "category": "Cashier and Front-End Services", - "hashtag": "#storejobs", + "hashtag": "#cosmeticscashierjobs", "summary": "Run the beauty counter register and keep the cosmetics department shoppable.", "do": [ "The cosmetics counter at {banner} #{store} in {city}, {state} has its own register and its own " @@ -546,7 +546,7 @@ "title": "Member Services Associate", "area": "stores-and-clubs", "category": "Cashier and Front-End Services", - "hashtag": "#samsclubjobs", + "hashtag": "#memberservicesjobs", "summary": "Sign up new members, renew memberships and solve problems at the member services desk.", "do": [ "At {banner} #{store} in {city}, {state} you own the member services desk: new sign-ups, " @@ -570,7 +570,7 @@ "title": "Food & Grocery Associate", "area": "stores-and-clubs", "category": "Food and Grocery", - "hashtag": "#storejobs", + "hashtag": "#foodandgroceryjobs", "summary": "Stock, rotate and merchandise the grocery aisles, coolers and freezers.", "do": [ "Food & Grocery Associates at {banner} #{store} in {city}, {state} unload the grocery truck, " @@ -597,7 +597,7 @@ "title": "Freezer/Cooler Associate", "area": "stores-and-clubs", "category": "Food and Grocery", - "hashtag": "#samsclubjobs", + "hashtag": "#freezercoolerjobs", "summary": "Work the club's freezer and cooler boxes, stocking bulk frozen and chilled product.", "do": [ "At {banner} #{store} in {city}, {state} you spend most of the shift inside the freezer and " @@ -622,7 +622,7 @@ "title": "General Merchandise Associate", "area": "stores-and-clubs", "category": "General Merchandise, Stocking, and Unloading", - "hashtag": "#storejobs", + "hashtag": "#generalmerchandisejobs", "summary": "Unload, sort and stock general merchandise across the sales floor.", "do": [ "General Merchandise Associates at {banner} #{store} in {city}, {state} unload trailers, sort " @@ -647,7 +647,7 @@ "title": "Stocking Associate", "area": "stores-and-clubs", "category": "General Merchandise, Stocking, and Unloading", - "hashtag": "#storejobs", + "hashtag": "#stockingassociatejobs", "summary": "Work the overnight stocking team, filling the store before the doors open.", "do": [ "Stocking Associates at {banner} #{store} in {city}, {state} work the truck overnight: unload, " @@ -670,7 +670,7 @@ "title": "Merchandising and Stocking Associate", "area": "stores-and-clubs", "category": "General Merchandise, Stocking, and Unloading", - "hashtag": "#samsclubjobs", + "hashtag": "#merchandisingjobs", "summary": "Build club pallets and keep the sales floor merchandised to plan.", "do": [ "At {banner} #{store} in {city}, {state} you stock bulk club pallets, build feature displays at " @@ -697,7 +697,7 @@ "title": "Online Order Filling Team Associate", "area": "stores-and-clubs", "category": "Digital Pickup and Delivery", - "hashtag": "#storejobs", + "hashtag": "#onlineorderfillingjobs", "summary": "Shop, stage and hand off customer pickup and delivery orders.", "do": [ "Online Order Filling Team Associates at {banner} #{store} in {city}, {state} shop customer " @@ -724,7 +724,7 @@ "title": "Online Order Filling Team Supervisor", "area": "stores-and-clubs", "category": "Digital Pickup and Delivery", - "hashtag": "#storejobs", + "hashtag": "#digitalpickupleadjobs", "summary": "Lead the pickup and delivery team through the day's order volume.", "do": [ "The Online Order Filling Team Supervisor at {banner} #{store} in {city}, {state} runs the " @@ -749,7 +749,7 @@ "title": "Cafe Associate", "area": "stores-and-clubs", "category": "Cafe", - "hashtag": "#samsclubjobs", + "hashtag": "#cafeassociatejobs", "summary": "Run the club cafe: prep, grill, serve and keep the counter to food safety standard.", "do": [ "Cafe Associates at {banner} #{store} in {city}, {state} take orders, prep and cook to the " @@ -774,7 +774,7 @@ "title": "Team Lead", "area": "stores-and-clubs", "category": "Retail Management", - "hashtag": "#storejobs", + "hashtag": "#teamleadjobs", "summary": "Lead a department team, own its standards and develop the associates on it.", "do": [ "Team Leads at {banner} #{store} in {city}, {state} run a department end to end: staffing the " @@ -800,7 +800,7 @@ "title": "Coach", "area": "stores-and-clubs", "category": "Retail Management", - "hashtag": "#storejobs", + "hashtag": "#storeleadershipjobs", "summary": "Lead several departments and the team leads who run them.", "do": [ "Coaches at {banner} #{store} in {city}, {state} lead a group of departments and the team leads " @@ -824,7 +824,7 @@ "title": "Fuel Station Associate", "area": "stores-and-clubs", "category": "Fuel Station", - "hashtag": "#samsclubjobs", + "hashtag": "#fuelstationjobs", "summary": "Run the club fuel station: assist members, check equipment and keep the site compliant.", "do": [ "Fuel Station Associates at {banner} #{store} in {city}, {state} greet members at the pumps, " @@ -850,7 +850,7 @@ "title": "Auto Care Center Technician", "area": "stores-and-clubs", "category": "Auto Care Center", - "hashtag": "#storejobs", + "hashtag": "#autocarecenterjobs", "summary": "Perform tire, battery and light maintenance service in the Auto Care Center.", "do": [ "Auto Care Center Technicians at {banner} #{store} in {city}, {state} mount and balance tires, " @@ -876,7 +876,7 @@ "title": "Tire & Battery Technician", "area": "stores-and-clubs", "category": "Auto Services", - "hashtag": "#samsclubjobs", + "hashtag": "#tireandbatteryjobs", "summary": "Service member vehicles in the club tire and battery center.", "do": [ "Tire & Battery Technicians at {banner} #{store} in {city}, {state} install and rotate tires, " @@ -901,7 +901,7 @@ "title": "Maintenance Technician", "area": "stores-and-clubs", "category": "Maintenance", - "hashtag": "#storejobs", + "hashtag": "#storemaintenancejobs", "summary": "Keep store equipment, refrigeration and building systems running.", "do": [ "Maintenance Technicians at {banner} #{store} in {city}, {state} respond to equipment calls " @@ -926,7 +926,7 @@ "title": "Asset Protection Associate", "area": "stores-and-clubs", "category": "Security and Asset Protection", - "hashtag": "#storejobs", + "hashtag": "#assetprotectionjobs", "summary": "Reduce shrink and keep associates and customers safe inside the store.", "do": [ "Asset Protection Associates at {banner} #{store} in {city}, {state} work the floor and the " @@ -952,7 +952,7 @@ "title": "Asset Protection Customer Specialist", "area": "stores-and-clubs", "category": "Security and Asset Protection", - "hashtag": "#storejobs", + "hashtag": "#apcustomerspecialistjobs", "summary": "Greet at the entrance, verify receipts and keep the front of the store secure.", "do": [ "Asset Protection Customer Specialists at {banner} #{store} in {city}, {state} work the " @@ -977,7 +977,7 @@ "title": "Pharmacy Technician", "area": "healthcare", "category": "Pharmacy Services", - "hashtag": "#healthcarejobs", + "hashtag": "#pharmacytechjobs", "summary": "Support the pharmacist with intake, data entry, filling and patient pickup.", "do": [ "Pharmacy Technicians at {banner} #{store} in {city}, {state} take in prescriptions, enter and " @@ -1003,7 +1003,7 @@ "title": "Certified Pharmacy Technician", "area": "healthcare", "category": "Pharmacy Services", - "hashtag": "#healthcarejobs", + "hashtag": "#certifiedpharmacytechjobs", "summary": "Work at the top of your certification supporting immunizations and clinical services.", "do": [ "Certified Pharmacy Technicians at {banner} #{store} in {city}, {state} do everything a " @@ -1027,7 +1027,7 @@ "title": "Optician", "area": "healthcare", "category": "Optical Services", - "hashtag": "#healthcarejobs", + "hashtag": "#opticianjobs", "summary": "Fit, adjust and dispense eyewear in the Vision Center.", "do": [ "Opticians at {banner} #{store} in {city}, {state} interpret prescriptions, take measurements, " @@ -1043,17 +1043,17 @@ "Complies with company policies, procedures, and standards of ethics and integrity.", ], "placements": [ - ("5991", "Full time", "WD,WE,SD", 22.00, 35.00, 2, None), ("2073", "Full time", "WD,SD", 21.50, 34.50, 1, None), ("5382", "Part time", "WE,SE", 23.00, 36.00, 1, None), ("1236", "Full time", "WD,WE", 21.00, 34.00, 1, None), + ("5991", "Full time", "WD,WE,SD", 22.00, 35.00, 2, None), ], }, { "title": "Vision Center Associate", "area": "healthcare", "category": "Optical Services", - "hashtag": "#healthcarejobs", + "hashtag": "#visioncenterjobs", "summary": "Greet vision center customers, schedule exams and support the optician.", "do": [ "Vision Center Associates at {banner} #{store} in {city}, {state} welcome customers, schedule " @@ -1076,7 +1076,7 @@ "title": "Health & Wellness Operations Associate", "area": "healthcare", "category": "Health and Wellness Operations", - "hashtag": "#healthcarejobs", + "hashtag": "#healthandwellnessjobs", "summary": "Keep the health and wellness area stocked, compliant and ready for patients.", "do": [ "Health & Wellness Operations Associates at {banner} #{store} in {city}, {state} own the " @@ -1102,7 +1102,7 @@ "title": "Certified Medical Assistant", "area": "healthcare", "category": "Clinical Care", - "hashtag": "#healthcarejobs", + "hashtag": "#clinicalcarejobs", "summary": "Room patients, take vitals and support the clinician in a community care setting.", "do": [ "Certified Medical Assistants supporting {banner} #{store} in {city}, {state} greet and room " @@ -1129,7 +1129,7 @@ "title": "Retail Operations Intern", "area": "students", "category": "Internship", - "hashtag": "#studentjobs", + "hashtag": "#walmartinternships", "summary": "A paid store internship rotating through front end, digital and merchandising.", "do": [ "Retail Operations Interns at {banner} #{store} in {city}, {state} spend the term rotating " @@ -1155,7 +1155,7 @@ "title": "Club Operations Intern", "area": "students", "category": "Internship", - "hashtag": "#studentjobs", + "hashtag": "#samsclubinternships", "summary": "A paid club internship focused on membership growth and fresh operations.", "do": [ "Club Operations Interns at {banner} #{store} in {city}, {state} work with the club manager on " diff --git a/sites/walmart_careers/seed_data.py b/sites/walmart_careers/seed_data.py index d7cb69a3..8655fb10 100644 --- a/sites/walmart_careers/seed_data.py +++ b/sites/walmart_careers/seed_data.py @@ -10,12 +10,14 @@ import json import os import random +import re import shutil from datetime import date, datetime, timedelta from pathlib import Path os.environ.setdefault("WEBSYN_SKIP_BOOTSTRAP", "1") +import _content as content_module import catalog_source as source from _content import MIRROR_REFERENCE_DATE from app import ( @@ -405,8 +407,47 @@ def _find_job(title: str, store_number: str) -> Job: # --------------------------------------------------------------------------- # # Build-time invariants. Only ever called from build_seed_database(). # --------------------------------------------------------------------------- # +# (task index, title, locator kwargs, expected job_id). The locator is what the +# task text tells an agent to look for; the check below proves it resolves to +# exactly one posting in the catalog. +TASK_TARGETS = [ + (0, "Optician", {"city": "Wichita"}, "CP-5991-11240"), + (1, "Staff, Software Engineer - Backend / ML", {"city": "Sunnyvale"}, "R-2463275"), + (2, "Freight Handler", {"store": "9054"}, "CP-9054-10921"), + (3, "Pharmacy Technician", {"city": "Bentonville"}, "CP-5260-11137"), + (4, "Merchandising and Stocking Associate", {"state": "TX"}, "CP-4750-11184"), + (5, "Senior Software Engineer", {"city": "Hoboken"}, "R-2411668"), + (6, "Online Order Filling Team Supervisor", {"city": "Cleveland"}, "CP-2073-10625"), + (7, "Merchandising Intern", {"store": "11109"}, "R-2442353"), + (8, "Auto Care Center Technician", {"city": "Brookhaven"}, "CP-1230-11711"), + (8, "Auto Care Center Technician", {"city": "Hazlehurst"}, "CP-954-11141"), + (9, "Freight Handler", {"store": "6038"}, "CP-6038-10642"), + (9, "Freight Handler", {"store": "9046"}, "CP-9046-10913"), + (10, "Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery)", + {"city": "Bentonville"}, "R-2451180"), + (10, "Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery)", + {"city": "Hoboken"}, "R-2439632"), + (11, "Yard Driver-Off Property", {"city": "Williamsburg"}, "CP-6088-10488"), + (12, "Asset Protection Associate", {"store": "5991"}, "CP-5991-10486"), + (13, "Pharmacy Technician", {"city": "Tacoma"}, "CP-4137-11560"), + (14, "eCom Warehouse Worker", {"store": "9046"}, "CP-9046-11101"), + (16, "Cashier & Front End Services", {"city": "Bayamon"}, "CP-2503-10726"), + (17, "Cosmetics Cashier", {"city": "Bayamon"}, "CP-2503-11683"), + (18, "Class A CDL Truck Driver", {"city": "Ottawa"}, "CP-6014-12091"), + (18, "Class A CDL Truck Driver", {"city": "Williamsburg"}, "CP-6088-11446"), + (19, "Online Order Filling Team Supervisor", {"city": "Topeka"}, "CP-1179-11268"), +] + + def _assert_distractors() -> None: - from app import Job as J, current_filters # noqa: F401 (kept for symmetry) + """Build-time invariants behind the 20 benchmark tasks. + + Only ever called from build_seed_database(); never at import, bootstrap or + /reset time. Every task in tasks.jsonl has a block here: the locator in the + task text must resolve to exactly one posting, the natural query must return + enough near-misses, and whatever the task asks the agent to report must have + a unique answer. + """ from app import search_jobs problems: list[str] = [] @@ -421,9 +462,36 @@ def base(**kwargs) -> dict: return filters def results(**kwargs) -> list[Job]: - jobs, _store, _failed = search_jobs(base(**kwargs)) + jobs, _location, _failed = search_jobs(base(**kwargs)) return jobs + def locate(title: str, *, city: str = "", state: str = "", store: str = "") -> list[Job]: + rows = Job.query.filter_by(title=title).all() + if city: + rows = [j for j in rows if j.store.city == city] + if state: + rows = [j for j in rows if j.store.state == state] + if store: + rows = [j for j in rows if j.store.store_number == store] + return sorted(rows, key=lambda j: j.job_id) + + def only(label: str, rows: list[Job], job_id: str) -> Job | None: + if len(rows) != 1: + problems.append(f"{label}: {len(rows)} postings match the task locator (want 1)") + return None + if rows[0].job_id != job_id: + problems.append(f"{label}: resolved to {rows[0].job_id}, expected {job_id}") + return rows[0] + + def breadth(label: str, rows: list[Job], full_matches: list[Job], minimum: int = 6) -> None: + """Catalog breadth rule: >= `minimum` results, <= 50% of them full matches.""" + if len(rows) < minimum: + problems.append(f"{label}: only {len(rows)} results (want >= {minimum})") + elif len(full_matches) > len(rows) / 2: + problems.append( + f"{label}: {len(full_matches)}/{len(rows)} results satisfy every constraint (want <= 50%)" + ) + # --- structural volumes ------------------------------------------------ if Job.query.count() != 200: problems.append(f"expected 200 jobs, found {Job.query.count()}") @@ -435,8 +503,8 @@ def results(**kwargs) -> list[Job]: count = Job.query.filter_by(store_id=store.id).count() if count < 3: problems.append(f"store #{store.store_number} has only {count} jobs") - states = {s.state for s in Store.query.all()} - for state in sorted(states): + states = sorted({s.state for s in Store.query.all()}) + for state in states: count = Job.query.join(Store).filter(Store.state == state).count() if count < 8: problems.append(f"state {state} has only {count} jobs") @@ -448,86 +516,217 @@ def results(**kwargs) -> list[Job]: if count < 24: problems.append(f"shift {name!r} appears on only {count} jobs") - # --- unique-answer invariants used by the task set --------------------- - pr_cashiers = [ - j for j in Job.query.join(Store).filter(Store.state == "PR").all() - if "Cashier" in j.title and j.population == "hourly" - ] - with_weekday_day = [j for j in pr_cashiers if "Weekday Day" in j.shifts] - if len(pr_cashiers) < 5: - problems.append(f"only {len(pr_cashiers)} PR cashier postings") - if len(with_weekday_day) < 3: - problems.append("fewer than 3 PR cashier postings list Weekday Day") - tops = sorted((j.positions_available for j in with_weekday_day), reverse=True) - if len(tops) >= 2 and tops[0] == tops[1]: - problems.append("PR Weekday Day cashier postings have a tied maximum of open positions") - if any(j.positions_available >= tops[0] for j in pr_cashiers if j not in with_weekday_day): - pass # a higher-positions near-miss without Weekday Day is intentional - - hoboken_tech = [ - j for j in results(area=["technology"], type=["Full time"], loc="Hoboken, NJ", radius=25) - ] - if len(hoboken_tech) < 6: - problems.append(f"only {len(hoboken_tech)} Full time Technology roles near Hoboken") - over_200k = [j for j in hoboken_tech if float(j.max_pay) > 200000] - if len(over_200k) != 1: - problems.append(f"{len(over_200k)} Hoboken Technology roles top out above $200,000 (want 1)") - + # --- global de-leak invariants ---------------------------------------- + hashtags = [f.get("hashtag") for f in source.HOURLY_FAMILIES] + if len(set(hashtags)) != len(hashtags): + problems.append("hourly hashtags are not unique per title family") + quals = [j.min_qualifications_json for j in Job.query.filter_by(population="salaried").all()] + if len(set(quals)) != len(quals): + problems.append("salaried minimum-qualification texts are not unique per posting") + target_ids = {job_id for _idx, _title, _loc, job_id in TASK_TARGETS} + for job_id in content_module.TRENDING_JOB_IDS: + if job_id in target_ids: + problems.append(f"trending role {job_id} is a benchmark task target") + if db.session.get(Job, job_id) is None: + problems.append(f"trending role {job_id} does not exist") + + # --- every task locator resolves to exactly one posting ---------------- + resolved: dict[tuple[int, str], Job] = {} + for index, title, locator, job_id in TASK_TARGETS: + label = f"task {index} ({title} {locator})" + job = only(label, locate(title, **locator), job_id) + if job is not None: + resolved[(index, job_id)] = job + + def target(index: int, job_id: str) -> Job | None: + return resolved.get((index, job_id)) + + # --- task 0: Optician in Wichita, KS ---------------------------------- + opticians = results(q="optician") + breadth("task 0 (q=optician)", opticians, + [j for j in opticians if j.title == "Optician" + and j.store.banner == "Neighborhood Market" and j.store.city == "Wichita"]) + + # --- task 1: Staff SWE in Sunnyvale — Option 2 unique among its family -- + staff = Job.query.filter_by(title="Staff, Software Engineer - Backend / ML").all() + if len({j.min_qualifications[1] for j in staff}) != len(staff): + problems.append("task 1: the Staff SWE postings share an Option 2 text") + + # --- task 2: Freight Handler #9054 — window + positions --------------- + freight = results(q="freight handler") + breadth("task 2 (q=freight handler)", freight, + [j for j in freight if j.title == "Freight Handler" and j.store.store_number == "9054"]) + + # --- task 3: Pharmacy Technician in Bentonville ----------------------- + pharmacy = [j for j in Job.query.all() if j.category and j.category.slug == "pharmacy-services"] + breadth("task 3 (Pharmacy Services category)", pharmacy, + [j for j in pharmacy if j.title == "Pharmacy Technician" and j.store.city == "Bentonville"]) + bentonville_pt = target(3, "CP-5260-11137") + if bentonville_pt is not None: + siblings = [j for j in Job.query.filter_by(title="Pharmacy Technician").all() + if j.job_id != bentonville_pt.job_id] + if any(j.hashtag != bentonville_pt.hashtag for j in siblings): + problems.append("task 3: Pharmacy Technician hashtags differ inside one title family") + if all(j.positions_available == bentonville_pt.positions_available for j in siblings): + problems.append("task 3: every Pharmacy Technician posting lists the same open positions") + + # --- task 4: Sam's Club / Part time / Weekend Overnight / <= $20 in TX -- sams_pt_overnight = results(brand=["Sam's Club"], type=["Part time"], shift=["Weekend Overnight"]) - if len(sams_pt_overnight) < 6: - problems.append( - f"only {len(sams_pt_overnight)} Sam's Club Part time Weekend Overnight roles" - ) - cheap_tx = [ - j for j in sams_pt_overnight - if float(j.max_pay) <= 20.00 and j.store.state == "TX" - ] + cheap = [j for j in sams_pt_overnight if float(j.max_pay) <= 20.00] + cheap_tx = [j for j in cheap if j.store.state == "TX"] + breadth("task 4 (Sam's Club PT weekend overnight)", sams_pt_overnight, cheap_tx) if len(cheap_tx) != 1: - problems.append(f"{len(cheap_tx)} Sam's Club PT overnight TX roles at or under $20/hr (want 1)") - full_matches = [j for j in sams_pt_overnight if float(j.max_pay) <= 20.00] - if len(full_matches) > len(sams_pt_overnight) / 2: - problems.append("more than half of the Sam's Club overnight results match every constraint") + problems.append(f"task 4: {len(cheap_tx)} matching TX postings at or under $20/hr (want 1)") + elif cheap_tx[0].job_id != "CP-4750-11184": + problems.append(f"task 4: resolved to {cheap_tx[0].job_id}") - cleveland = results(loc="Cleveland, OH", radius=25) - if len(cleveland) < 6: - problems.append(f"only {len(cleveland)} roles within 25 miles of Cleveland, OH") + # --- task 5: Full time Technology in Hoboken topping $200,000 --------- + hoboken_tech = results(area=["technology"], type=["Full time"], loc="Hoboken, NJ", radius=25) + over_200k = [j for j in hoboken_tech if float(j.max_pay) > 200000] + breadth("task 5 (Full time Technology near Hoboken)", hoboken_tech, over_200k) + if len(over_200k) != 1: + problems.append(f"task 5: {len(over_200k)} Hoboken tech roles top out above $200,000 (want 1)") - ms_auto = [ - j for j in Job.query.join(Store).filter(Store.state == "MS").all() - if j.title == "Auto Care Center Technician" - ] + # --- task 6: Cleveland, OH / Full time / Weekday Day ------------------ + cleveland = results(loc="Cleveland, OH", radius=25) + cleveland_full = results(loc="Cleveland, OH", radius=25, type=["Full time"], shift=["Weekday Day"]) + supervisors = [j for j in cleveland_full if j.title == "Online Order Filling Team Supervisor"] + breadth("task 6 (within 25 miles of Cleveland)", cleveland, supervisors) + if len(supervisors) != 1: + problems.append(f"task 6: {len(supervisors)} Online Order Filling Team Supervisor roles near Cleveland") + + # --- task 7: Students / Intern / Sam's Club --------------------------- + interns = results(area=["students"], type=["Intern"]) + sams_interns = [j for j in interns if j.brand == "Sam's Club"] + merch_interns = [j for j in sams_interns if "Merchandising" in j.title] + breadth("task 7 (Students interns)", interns, merch_interns) + if len(merch_interns) != 1: + problems.append(f"task 7: {len(merch_interns)} Sam's Club merchandising internships (want 1)") + elif merch_interns[0].store.street != "2101 SE Simple Savings Dr": + problems.append("task 7: the Sam's Club internship street address moved") + + # --- task 8: two MS Auto Care postings, different position counts ------ + ms_auto = [j for j in Job.query.join(Store).filter(Store.state == "MS").all() + if j.title == "Auto Care Center Technician"] if len(ms_auto) != 2: - problems.append(f"{len(ms_auto)} Auto Care Center Technician postings in MS (want 2)") + problems.append(f"task 8: {len(ms_auto)} Auto Care Center Technician postings in MS (want 2)") elif ms_auto[0].positions_available == ms_auto[1].positions_available: - problems.append("the two MS Auto Care postings have the same number of open positions") + problems.append("task 8: the two MS Auto Care postings list the same open positions") - marcy_freight = [ - j for j in Job.query.join(Store).filter(Store.city == "Marcy").all() - if j.title == "Freight Handler" - ] + # --- task 9: two Marcy Freight Handlers, different windows ------------ + marcy_freight = [j for j in Job.query.join(Store).filter(Store.city == "Marcy").all() + if j.title == "Freight Handler"] if len(marcy_freight) != 2: - problems.append(f"{len(marcy_freight)} Freight Handler postings in Marcy, NY (want 2)") + problems.append(f"task 9: {len(marcy_freight)} Freight Handler postings in Marcy, NY (want 2)") elif marcy_freight[0].shift_time == marcy_freight[1].shift_time: - problems.append("the two Marcy Freight Handler postings share a shift start window") + problems.append("task 9: the two Marcy Freight Handler postings share a shift start window") + # --- task 10: two Last Mile postings, different Option 2 years -------- last_mile = Job.query.filter(Job.title.like("Senior Manager, Delivery Search%")).all() if len(last_mile) != 2: - problems.append(f"{len(last_mile)} Last Mile Delivery postings (want 2)") + problems.append(f"task 10: {len(last_mile)} Last Mile Delivery postings (want 2)") else: - options = [j.min_qualifications[1] for j in last_mile] - if options[0] == options[1]: - problems.append("the two Last Mile Delivery postings share Option 2 text") - - # Qualification text referenced by tasks must be unique per posting. - quals = [j.min_qualifications_json for j in Job.query.filter_by(population="salaried").all()] - if len(set(quals)) != len(quals): - problems.append("salaried minimum-qualification texts are not unique per posting") - - # Targets must not be pinned to rank 1 of their own natural query. - for query, title in (("optician", "Optician"), ("freight handler", "Freight Handler")): + years = [_years_in(j.min_qualifications[1]) for j in last_mile] + if years[0] == years[1] or None in years: + problems.append("task 10: the two Last Mile postings do not differ in Option 2 years") + + # --- task 11: Yard Driver search set --------------------------------- + yard = results(q="yard driver") + breadth("task 11 (q=yard driver)", yard, + [j for j in yard if j.title == "Yard Driver-Off Property" + and j.store.city == "Williamsburg"]) + + # --- tasks 12 / 17: the seeded saved lists must disambiguate ---------- + for email, predicate, label in ( + ("bob.c@test.com", lambda j: j.store.banner == "Neighborhood Market", + "task 12 (bob's Neighborhood Market saved role)"), + ("alice.j@test.com", lambda j: j.employment_type == "Part time", + "task 17 (alice's Part time saved role)"), + ): + user = User.query.filter_by(email=email).one() + rows = [db.session.get(Job, s.job_id) for s in + SavedJob.query.filter_by(user_id=user.id).order_by(SavedJob.id).all()] + if len(rows) < 3: + problems.append(f"{label}: only {len(rows)} saved roles (want >= 3)") + matches = [j for j in rows if predicate(j)] + if len(matches) != 1: + problems.append(f"{label}: {len(matches)} saved roles match (want exactly 1)") + + # --- task 13: Pharmacy Technician in Tacoma -------------------------- + tacoma = results(q="pharmacy technician") + breadth("task 13 (q=pharmacy technician)", tacoma, + [j for j in tacoma if j.title == "Pharmacy Technician" and j.store.city == "Tacoma"]) + + # --- task 14: eCom Warehouse Worker at #9046 ------------------------- + ecom = results(q="ecom warehouse worker") + breadth("task 14 (q=ecom warehouse worker)", ecom, + [j for j in ecom if j.title == "eCom Warehouse Worker" + and j.store.store_number == "9046"]) + + # --- task 15: david's profile starts different from the target values -- + david = User.query.filter_by(email="david.k@test.com").one() + if david.phone == "479-555-0199" or (david.city, david.state) == ("Rogers", "AR"): + problems.append("task 15: david's seeded profile already holds the target values") + + # --- task 16: PR cashiers --------------------------------------------- + pr_cashiers = [j for j in Job.query.join(Store).filter(Store.state == "PR").all() + if "Cashier" in j.title and j.population == "hourly"] + with_weekday_day = [j for j in pr_cashiers if "Weekday Day" in j.shifts] + if len(pr_cashiers) < 5: + problems.append(f"task 16: only {len(pr_cashiers)} PR cashier postings") + if len(with_weekday_day) < 3: + problems.append("task 16: fewer than 3 PR cashier postings list Weekday Day") + if len(with_weekday_day) > len(pr_cashiers) / 2 and len(pr_cashiers) - len(with_weekday_day) < 1: + problems.append("task 16: every PR cashier posting lists Weekday Day — no near-miss") + tops = sorted((j.positions_available for j in with_weekday_day), reverse=True) + if len(tops) >= 2 and tops[0] == tops[1]: + problems.append("task 16: the PR Weekday Day cashiers tie on open positions") + without = [j for j in pr_cashiers if j not in with_weekday_day] + if tops and not any(j.positions_available >= tops[0] - 1 for j in without): + problems.append("task 16: no near-miss PR cashier with a comparable open-position count") + # both routes to the Puerto Rico result set must work + by_state = results(q="cashier", loc="Puerto Rico") + by_radius = results(q="cashier", loc="Bayamon, PR", radius=60) + for label, rows in (("loc=Puerto Rico", by_state), ("loc=Bayamon, PR r=60", by_radius)): + reachable = {j.job_id for j in rows} + missing = [j.job_id for j in pr_cashiers if j.job_id not in reachable] + if missing: + problems.append(f"task 16: {label} misses PR cashier postings {missing}") + + # --- task 18: Drivers category, Ottawa vs Williamsburg ---------------- + drivers = results(area=["supply-chain-and-transportation"], category=["drivers"]) + cdl = [j for j in drivers if j.title == "Class A CDL Truck Driver" + and j.store.city in ("Ottawa", "Williamsburg")] + breadth("task 18 (Drivers category)", drivers, cdl, minimum=6) + ottawa = target(18, "CP-6014-12091") + williamsburg = target(18, "CP-6088-11446") + if ottawa is not None and williamsburg is not None: + if ottawa.positions_available == williamsburg.positions_available: + problems.append("task 18: the two CDL postings list the same open positions") + if ottawa.shift_time == williamsburg.shift_time: + problems.append("task 18: the two CDL postings share a shift start window") + + # --- task 19: Digital Pickup and Delivery, Full time, fewest positions -- + pickup = results(area=["stores-and-clubs"], category=["digital-pickup-and-delivery"]) + pickup_full = [j for j in pickup if j.employment_type == "Full time"] + counts = sorted(j.positions_available for j in pickup_full) + breadth("task 19 (Digital Pickup and Delivery)", pickup, + [j for j in pickup_full if counts and j.positions_available == counts[0]]) + if len(counts) < 3: + problems.append(f"task 19: only {len(counts)} Full time digital pickup postings") + elif counts[0] == counts[1]: + problems.append("task 19: the fewest-open-positions posting is not unique") + + # --- relevance: no target is pinned to rank 1 of its natural query ----- + for index, query, job_id in ( + (0, "optician", "CP-5991-11240"), + (2, "freight handler", "CP-9054-10921"), + (13, "pharmacy technician", "CP-4137-11560"), + ): rows = results(q=query) - if len(rows) < 6: - problems.append(f"query {query!r} returns only {len(rows)} results") + ids = [j.job_id for j in rows] + if ids[:1] == [job_id]: + problems.append(f"task {index}: the target is the first result for {query!r}") if problems: raise AssertionError( @@ -535,6 +734,11 @@ def results(**kwargs) -> list[Job]: ) +def _years_in(text: str) -> int | None: + match = re.search(r"(\d+)\s+years", text) + return int(match.group(1)) if match else None + + def build_seed_database() -> None: INSTANCE_SEED_DIR.mkdir(parents=True, exist_ok=True) DB_PATH.parent.mkdir(parents=True, exist_ok=True) diff --git a/sites/walmart_careers/templates/saved_roles.html b/sites/walmart_careers/templates/saved_roles.html index 00617d8a..c7defbe2 100644 --- a/sites/walmart_careers/templates/saved_roles.html +++ b/sites/walmart_careers/templates/saved_roles.html @@ -19,12 +19,14 @@

You haven't saved any roles yet

Browse open roles
{% else %} -
+
{% for row in rows %}{{ job_card(row.job, saved_ids) }}{% endfor %}
{% endif %}
+{# Recommendations only fill the page when there is nothing saved to show. #} +{% if not current_user.is_authenticated or not rows %}

Trending roles

@@ -33,4 +35,5 @@

Trending roles

+{% endif %} {% endblock %} From ddc6355e38966cb975bb0cbe4196b9e24cd27604 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:04:54 -0500 Subject: [PATCH 06/15] docs(walmart_careers): record the frozen seed and the verification run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 (seed-database). The seed database is frozen and reproducible: two consecutive `PYTHONHASHSEED=0 python seed_data.py` runs produce the identical file (md5 faf1c03a780314e71f9586d5b0edc69b), and inside the container the instance and instance_seed copies match both after POST /reset/walmart_careers and after a docker restart. VERIFICATION.md records the md5 outputs, seeded rows per model, the 20/20 task walkthrough (driven through Chromium with a control-plane reset before each task), the answer-leak task x page matrix (0 leaks), the near-miss and breadth measurements, 26/26 interaction-robustness checks, the visual-fidelity diff against scraped_data/reference/*.png, every file that references port 40017, and the list of things that still need human judgment — including that tasks 13 and 17 both yield WMC-000005 when each is run from a fresh reset. instance_seed/walmart_careers.db itself is HF-managed and gitignored; it ships in the assets PR, pinned afterwards through .assets-revision. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Uso3TzMAGoE9CanJMW6wcJ --- sites/walmart_careers/CLAUDE.md | 23 +- sites/walmart_careers/VERIFICATION.md | 370 ++++++++++++++++++++++++++ 2 files changed, 390 insertions(+), 3 deletions(-) create mode 100644 sites/walmart_careers/VERIFICATION.md diff --git a/sites/walmart_careers/CLAUDE.md b/sites/walmart_careers/CLAUDE.md index 852df243..b3a126a3 100644 --- a/sites/walmart_careers/CLAUDE.md +++ b/sites/walmart_careers/CLAUDE.md @@ -11,8 +11,9 @@ alt-port test container maps it to **41017**. | `catalog_source.py` | the source catalog: areas, categories, 44 stores, 37 hourly + 29 salaried title families with explicit placements | | `seed_data.py` | turns the catalog into SQLite; `build_seed_database()` is the freezer | | `_content.py` | CMS-style prose, design constants, US/PR map outlines | -| `templates/` | 18 Jinja2 templates + `_job_card.html` macro | -| `scripts_dev/` | local-only helpers (asset harvest, smoke test); gitignored and dockerignored | +| `templates/` | 19 Jinja2 templates + `_job_card.html` macro | +| `scripts_dev/` | local-only helpers; gitignored and dockerignored | +| `VERIFICATION.md` | what was verified, how, and what still needs human judgment | ## Rebuilding the seed DB @@ -24,7 +25,10 @@ PYTHONHASHSEED=0 python seed_data.py # writes instance_seed/walmart_careers. Run it twice and compare md5s — the build is byte-reproducible. `build_seed_database()` also runs `_assert_distractors()`, which fails the build if a catalog edit breaks a volume invariant (jobs per category/store/state/shift) or a task's near-miss set. -`_assert_distractors()` never runs at import or at `/reset` time. +`_assert_distractors()` never runs at import or at `/reset` time. It covers all 20 +tasks: `TASK_TARGETS` proves each task's locator (title + city, or title + store) +resolves to exactly one posting, and each task block checks its result set has ≥6 rows +with ≤50 % of them satisfying every constraint the task states. ## Determinism rules that must hold @@ -34,6 +38,19 @@ volume invariant (jobs per category/store/state/shift) or a task's near-miss set - the four werkzeug password hashes are hard-coded (werkzeug salts randomly) - `seed_database()` and `seed_benchmark_users()` are each gated as a whole +## Local dev helpers (`scripts_dev/`, never shipped) + +```bash +python scripts_dev/serve.py 5017 # run with Jinja auto-reload on +python scripts_dev/walkthrough.py [] +python scripts_dev/leak_audit.py # writes leak_audit.md +python scripts_dev/robustness.py +python scripts_dev/shots.py # 1440px screenshots +``` + +`walkthrough.py`, `leak_audit.py` and `robustness.py` hold the ground-truth answers, +which is exactly why `scripts_dev/` is in both `.gitignore` and `.dockerignore`. + ## Assets Brand chrome (`static/icons/`, `static/fonts/`) is committed; photography diff --git a/sites/walmart_careers/VERIFICATION.md b/sites/walmart_careers/VERIFICATION.md new file mode 100644 index 00000000..4c6982cc --- /dev/null +++ b/sites/walmart_careers/VERIFICATION.md @@ -0,0 +1,370 @@ +# walmart_careers — verification record + +Everything below was executed against this working tree on 2026-09-06. Commands that +say "container" ran against `webharbor:dev` started as + +```bash +docker run -d --rm --name wh-test -p 8201:8101 -p 41000-41017:40000-40017 webharbor:dev +``` + +The dev-only drivers used here live in `sites/walmart_careers/scripts_dev/` +(`walkthrough.py`, `leak_audit.py`, `robustness.py`, `shots.py`, `serve.py`). +That directory is gitignored **and** dockerignored, so the answer keys those +scripts contain never ship with the site or the image. + +--- + +## 1. Byte-identical reset + +### Seed build reproducibility (two consecutive freezer runs) + +``` +$ PYTHONHASHSEED=0 python seed_data.py # run 1 +$ md5 -q instance_seed/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b +$ PYTHONHASHSEED=0 python seed_data.py # run 2 +$ md5 -q instance_seed/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b +``` + +### Container: after `POST /reset/walmart_careers` + +``` +$ curl -X POST http://localhost:8201/reset/walmart_careers +{"pid":2506,"ready":true,"site":"walmart_careers"} + +$ docker exec wh-test md5sum \ + /opt/WebSyn/walmart_careers/instance/walmart_careers.db \ + /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db +``` + +### Container: after `docker restart wh-test` + +``` +$ docker restart wh-test +$ docker exec wh-test md5sum \ + /opt/WebSyn/walmart_careers/instance/walmart_careers.db \ + /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance/walmart_careers.db +faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db +``` + +Both md5s match in both directions. Bootstrap seeding is gated per whole function +(`seed_database()` on `Job.query.count() > 0`, `seed_benchmark_users()` on the +presence of `alice.j@test.com`), so a populated DB triggers no commit at all. + +### All 18 sites still serve + +``` +$ for p in $(seq 41000 41017); do curl -so /dev/null -w "$p %{http_code}\n" http://localhost:$p/; done +41000 200 … 41017 200 # all eighteen returned 200 +$ curl -s http://localhost:8201/health # 18 sites, alive: true for every one +$ curl -s http://localhost:41017/_health +{"areas":7,"categories":33,"jobs":200,"ok":true,"site":"walmart_careers","stores":44,"users":4} +``` + +--- + +## 2. Seeded rows per model + +| model | rows | +|---|---| +| `Area` | 7 (6 filterable career areas + Military) | +| `Category` | 33 | +| `Store` | 44 (6 offices + 38 field locations, 13 states/territories) | +| `Job` | 200 | +| `User` | 4 benchmark users, password `TestPass123!` | +| `SavedJob` | 10 (alice 3, bob 3, carol 2, david 2) | +| `Application` | 4 (`WMC-000001` … `WMC-000004`) | + +Job distribution: + +| dimension | breakdown | +|---|---| +| population | hourly 140 / salaried 60 | +| brand | Walmart 160 / Sam's Club 34 / Vizio 6 | +| employment type | Full time 120 / Part time 68 / Intern 12 | +| career area | Stores and Clubs 72, Supply Chain and Transportation 42, Technology 30, Corporate 24, Healthcare 20, Students 12 | +| distinct titles | 66 | +| shifts | every one of the 7 values appears on ≥24 postings | + +`_assert_distractors()` (freezer-only) also enforces: ≥4 jobs per category, ≥3 per +store, ≥8 per state, unique hashtags per hourly title family, unique minimum +qualification texts per salaried posting, and no trending role that is a task target. + +--- + +## 3. Task walkthroughs — 20/20 + +`scripts_dev/walkthrough.py` drives every task through Chromium (header search box, +Filters popover checkboxes, the Location popover, real login/apply/save/unsave form +submissions). Run against the container with a control-plane reset before each task, +so every task starts from the frozen seed state: + +``` +$ python scripts_dev/walkthrough.py http://localhost:41017 \ + http://localhost:8201/reset/walmart_careers +``` + +| task | what the walkthrough read off the page | +|---|---| +| 0 | `CP-5991-11240` / `2441 S Rock Rd` | +| 1 | `Option 2: 6 years' experience in software engineering or related area.` | +| 2 | `Shift may start between 6:00pm - 3:00am` / 3 open positions | +| 3 | `#pharmacytechjobs` / 2 open positions | +| 4 | `CP-4750-11184` / 3 open positions | +| 5 | `R-2411668` / Option 1 degree field | +| 6 | `10000 Brookpark Rd` / 2 open positions | +| 7 | `Intern (Fixed Term)` / `2101 SE Simple Savings Dr` | +| 8 | store `#1230` with 5 open positions | +| 9 | `CP-6038-10642` / `Shift may start between 3:00pm - 7:30pm` | +| 10 | `R-2451180` / 7 years | +| 11 | `CP-6088-10488` saved to alice's saved roles | +| 12 | the Neighborhood Market saved role removed from bob's list | +| 13 | `WMC-000005` | +| 14 | new account registered + `CP-9046-11101` saved | +| 15 | david's phone/city updated and persisted | +| 16 | `CP-2503-10726` / 5 open positions | +| 17 | `Shift may start between 6:00am - 11:00am` / `WMC-000005` | +| 18 | `CP-6014-12091` / `Shift may start between 5:00am - 10:00am` / 4 open positions | +| 19 | `CP-1179-11268` / `1301 SW Wanamaker Rd` | + +**Note for the reviewer — confirmation numbers collide across tasks 13 and 17.** +The seed holds four applications, so the first application submitted against a freshly +reset database is always id 5 → `WMC-000005`. Tasks 13 and 17 therefore both produce +`WMC-000005` when each is run from a fresh reset (as above). This is by design: the +numbering scheme is `"WMC-" + zero-padded application id` and was left unchanged. A +verifier must not treat the confirmation number as a task-unique value — it should +match the `applications` row by `job_id` + `email` and then compare +`confirmation_no`. If both tasks run in the same session without a reset in between, +task 17 yields `WMC-000006`. + +--- + +## 4. Answer-leak audit (task × page matrix) + +Produced by `scripts_dev/leak_audit.py` against the **running container's rendered +HTML** (raw HTML, so `display:none` blocks and HTML comments are included). +`clean` = the answer token appears nowhere in the page. `n/a (stateful)` = the task's +deliverable is a database change, not a string read off a page. + +Requisition IDs are part of every posting URL by design; `href`, `action` and hidden +`value` attributes are therefore stripped before a token is judged readable, which is +exactly why no task in this set answers with a requisition ID alone. + +| task | home | results (natural query) | career area page | /resources/location | results URL audited | +|---|---|---|---|---|---| +| 0 | clean | clean | clean | clean | /results?q=optician | +| 1 | clean | clean | clean | clean | /results?q=staff+software+engineer | +| 2 | clean | clean | clean | clean | /results?q=freight+handler | +| 3 | clean | clean | clean | clean | /results?q=pharmacy+technician | +| 4 | clean | clean | clean | clean | /results?brand=Sam's+Club&type=Part+time&shift=Weekend+Overnight | +| 5 | clean | clean | clean | clean | /results?area=technology&type=Full+time&loc=Hoboken,+NJ&radius=25 | +| 6 | clean | clean | clean | clean | /results?loc=Cleveland,+OH&radius=25&type=Full+time&shift=Weekday+Day | +| 7 | clean | clean | clean | clean | /results?area=students&type=Intern&brand=Sam's+Club | +| 8 | clean | clean | clean | clean | /results?q=auto+care+center+technician | +| 9 | clean | clean | clean | clean | /results?q=freight+handler | +| 10 | clean | clean | clean | clean | /results?q=delivery+search+arrival+matching | +| 11 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=yard+driver | +| 12 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=asset+protection | +| 13 | clean | clean | clean | clean | /results?q=pharmacy+technician | +| 14 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=ecom+warehouse+worker | +| 15 | clean | clean | clean | clean | /results?q= | +| 16 | clean | clean | clean | clean | /results?q=cashier&loc=Puerto+Rico | +| 17 | clean | clean | clean | clean | /candidate-home/saved-roles | +| 18 | clean | clean | clean | clean | /results?area=supply-chain-and-transportation&category=drivers | +| 19 | clean | clean | clean | clean | /results?area=stores-and-clubs&category=digital-pickup-and-delivery&type=Full+time | + +**0 leaks.** Tokens audited are the detail-only fields each task asks for: street +addresses, open-position counts, shift start windows, hashtags, worker-type chips, +qualification texts, years of experience and confirmation numbers. + +### The 13 leak archetypes + +| # | archetype | status | +|---|---|---| +| 1 | numeric difference pre-computed | tasks 8/10/18/19 make the agent open both postings and compare; no page states the delta | +| 2 | count the agent should count | task 16 requires visiting each PR cashier posting; the results heading counts roles, never open positions | +| 3 | verbatim task framing echoed | the results `

`/`` are `N open roles` — the query is no longer echoed | +| 4 | pre-bundled answer sentence | body copy is generated from per-family templates with store/shift/pay slots; no sentence restates a task answer | +| 5 | pinned/highlighted answer callout | no callouts; the fact column is identical for every posting | +| 6 | spoon-fed list endings with count | the "What you'll bring" list has no trailing count | +| 7 | wiki paragraph matching the question | n/a — no article pages | +| 8 | operand-only fuzzy match in the backend | search scores over title+category+area+banner+city+state+brand; `description` is deliberately excluded from the blob | +| 9 | bare-anchor → answer-bucket flood | the map is a deterministic SVG of the current result set; it carries city names and counts, never postings | +| 10 | algorithm-revealing UI text | the sort control says "Relevance"/"Most recent" only | +| 11 | sort order putting the answer first | the freezer asserts the target is not rank 1 for its natural query (tasks 0/2/13); relevance ties break on a seeded shuffle | +| 12 | pre-curated lookup table | none; every fact comes from SQLAlchemy | +| 13 | constraint values in item names | titles carry no shift, state, brand or pay words; the freezer's per-task locator check keeps each title+city pair unique | + +--- + +## 5. Near-miss distractors and catalog breadth + +`_assert_distractors()` enforces, per task, ≥6 results on the task's natural query or +facet set with ≤50 % of them satisfying *every* stated constraint. Measured on the +frozen seed: + +| task | result set | size | full matches | +|---|---|---|---| +| 0 | `q=optician` | 6 | 1 | +| 2 | `q=freight handler` | 6 | 1 | +| 3 | Pharmacy Services category | 6 | 1 | +| 4 | Sam's Club · Part time · Weekend Overnight | 8 | 1 (3 are ≤ $20/hr, only one of those is in TX) | +| 5 | Full time Technology within 25 mi of Hoboken | 6 | 1 | +| 6 | within 25 mi of Cleveland, OH | 8 (4 after Full time + Weekday Day) | 1 | +| 7 | Students · Intern | 12 | 1 | +| 11 | `q=yard driver` | 8 | 1 | +| 13 | `q=pharmacy technician` | 60 | 1 | +| 14 | `q=ecom warehouse worker` | 19 | 1 | +| 16 | PR hourly cashier postings | 5 (4 list Weekday Day) | 1 | +| 18 | Drivers category | 8 | 2 | +| 19 | Digital Pickup and Delivery | 8 (4 Full time) | 1 | + +Deliberate near-misses: Ponce PR has the second-highest open-position count among PR +cashiers but does not list Weekday Day (task 16); the Plano TX Sam's Club has two Part +time Weekend Overnight postings and only one is at or under $20/hr (task 4); Marcy NY +carries two Freight Handler postings at different facilities with different windows +(task 9); the second Merchandising Intern sits at the Walmart home office rather than +the Sam's Club one (task 7). + +--- + +## 6. Interaction robustness — 26/26 + +`scripts_dev/robustness.py`, against the container: + +- partial and loose queries return the right family (`cashi`, `freight hand`, + `optical` → Optician, `sams club`, `truck driver`) +- an unresolvable location renders "We couldn't find that location", not an empty page +- a wrong password is rejected; `/account`, `/account/edit` and + `/candidate-home/applications` redirect to `/login`; `/candidate-home/saved-roles` + renders logged out with a sign-in CTA +- the apply form validates client-side *and* server-side (a raw POST with an empty + body and one with a malformed email are both rejected with field errors) +- registration rejects a duplicate email, a short password and a password mismatch +- an anonymous save redirects to `/login?next=`; a signed-in save and unsave both + persist across a reload +- a POST without a CSRF token returns 400 +- an unknown job id renders the 404 page + +--- + +## 7. Visual fidelity + +Screenshots at 1440 px are in `scraped_data/mirror/`, using the same filenames as +`scraped_data/reference/` (`scripts_dev/shots.py`; `scraped_data/` is gitignored, so +these are local review artefacts). + +Fixed in this run: + +- **Header** now mirrors the live bar exactly: the full spark + `<>` + "Careers" + lockup, then `Career areas` (dropdown listing all six areas), `Brands`, `Resources`, + `About Us`, `Military`, a white search pill with a blue circular search button, and a + user icon whose popover holds *My account / Saved roles / Login/Signup / EN* (or the + initials avatar plus *My applications / Log out* when signed in). Nothing wraps at + 1440 px; the header spans the full viewport width like the original. +- **Results page**: heading is `N open roles` with no query echo; the count badge is on + the `Open roles` tab only; `Add your location` is a link that opens a Location + popover; `Filters` is a button that opens the facet panel; `Sort by: Relevance` is a + dropdown. The permanently expanded sidebar panels are gone — the left column is the + map only. Cards are population-aware: salaried cards show title / `City, ST zip` / + `shift • $x - $y/yr`; hourly cards add the `banner #store` line above the city. Both + buttons are styled as the live outlined `Select +` pill. +- **Job detail**: two layouts branched on `job.population`, both matching their + reference screenshot — the three-photo masthead with the identity card (solid + ld-blue for salaried, blue-over-navy for hourly), the left `Role Details` rail + (sub-items only for hourly), the title, the address block with the map card, and the + three dark navy chips (pay / worker type / Salaried for salaried; pay / employment + type / shift window for hourly). `Apply now` is ld-blue on both. +- **Home**: centred hero with the inset dark search pill and circular button, plus the + three-photo strip that overlaps the blue/white boundary; trending roles are three + across on white. + +Remaining differences from `scraped_data/reference/*.png`: + +1. **The maps are deterministic SVGs, not Google Maps.** The results cluster map is a + stylised US+PR outline with bubbles positioned from the seeded store coordinates; + the detail page shows an SVG map card with a pin instead of a Google tile. This was + the explicit decision in the build brief (PLAN.md §7.9 rejected). The mirror map is + also less zoomed than the reference, which frames the whole western hemisphere. +2. **No `Chat` accordion and no `Go back` link** in the results sidebar. The chat panel + is the site's LLM search assistant, which the mirror deliberately does not + reproduce; `Go back` is a browser-history control with no server-side meaning. +3. **`Future roles` and `Content` tabs carry no count badge** and open an explicit + empty-state panel. The brief asked for a badge on `Open roles` only; the live site + shows counts on all three. +4. **The Filters popover is a four-column panel**, not the live single column. The + mirror exposes more facets at once (Brand, Shift, Employment Type + Rate, Career + Area with nested categories); a single column would need scrolling to reach the + career areas that tasks 5, 7, 18 and 19 depend on. +5. **The salaried card keeps the ZIP and the shift prefix** (`Sunnyvale, CA 94089-4731` + / `Multiple shifts • $143,000 - $286,000/yr`). The run instruction said "title / + City, ST / pay only", but `reference/home.png`'s trending cards show the ZIP and the + shift prefix on salaried cards, so the reference was followed. The banner line — the + part that genuinely differs between populations — is dropped for offices. +6. **The salaried detail page keeps the three-photo masthead.** The run instruction + described the salaried layout as "no hero photos", but `reference/job_detail_corp.png` + shows the same three-photo masthead as the hourly page (with corporate photography + rather than store photography), so the reference was followed. The two layouts still + branch on `job.population` for the identity-card colour, the left rail's sub-items, + the open-positions pill and the chip set. +7. **`About Us` links to a local `/about-us` page** assembled from the existing CMS + constants. The live nav item points at an off-domain corporate site, which is out of + scope for an offline mirror. +8. Minor typography drift: the mirror uses the harvested `EverydaySansUI` variable + font, so line breaks inside long body paragraphs differ slightly from the reference + captures. + +--- + +## 8. Files that reference port 40017 + +| file | reference | +|---|---| +| `websyn_start.sh` | `walmart_careers` is index 17 of `SITES=( … )` → 40000 + 17 | +| `control_server.py` | `'walmart_careers'` is the 18th entry of `SITES` (same order) | +| `Dockerfile` | `EXPOSE 8101 40000-40017` | +| `sites/walmart_careers/tasks.jsonl` | `"web": "http://localhost:40017/"` on all 20 rows | +| `sites/walmart_careers/CLAUDE.md` | "Port **40017** … alt-port **41017**" | +| `README.md` | `-p 40000-40017:40000-40017`, and the 18-mirror list | +| `AGENTS.md` | three `40000-40017` occurrences plus `41000-41017` in the pre-PR block | +| `CONTRIBUTING.md` | TL;DR `-p 40000-40017:40000-40017` | +| `CLAUDE.md` | `:40000-40017` / `:41000-41017` in "Existing containers" | + +Reassigning the slot is a single `sed` over `40017`/`41017` plus moving the entry in +the two `SITES` lists. Nothing else hard-codes the port; the dev-only scripts under +`scripts_dev/` take the base URL as an argument. + +--- + +## 9. Needs human judgment + +1. **Confirmation numbers repeat across tasks 13 and 17** (`WMC-000005` from a fresh + reset each). See §3 — the reviewer's verifiers should match the `applications` row, + not assume a unique string. Flagged rather than changed because the brief fixed the + numbering scheme. +2. **Two run-instruction deviations, both resolved in favour of the reference + screenshots**: salaried cards keep ZIP + shift prefix, and the salaried detail page + keeps its three-photo masthead (§7 items 5 and 6). Say the word and both flip to the + literal instruction. +3. **Task 2's `upstream_url`** points at `…/jobs/CP-9046-11101`, a real posting URL of + the right page type, but the same posting task 14 references. Harmless (the field is + documentation of the upstream page shape) but a reviewer may prefer a distinct URL. +4. **Location search accepts a whole state or territory** ("Puerto Rico", "PR", "Ohio") + and then ignores the radius. The live site only geocodes cities/ZIPs. This was added + so task 16 has a reliable route to every Puerto Rico posting; the radius route + (`loc=Bayamon, PR`, 60 miles) also reaches all five and is asserted at build time. + Drop it if the reviewer considers it too much of a mirror-only affordance. +5. **`Students` has no career-area index page** (`has_index_page = False`, matching the + live site), so the "Career areas" menu sends it to `/results?area=students`. Task 7 + reaches it through the Filters panel, which is what its wording asks for. +6. **The Optician family's placement order was reordered** in `catalog_source.py` so the + task 0 target is not rank 1 for `q=optician`. That shifts the seeded requisition IDs + for the four Optician postings (the target is now `CP-5991-11240`). Any verifier + drafted against an earlier build of this DB must be re-derived from the frozen seed. +7. **The mirror's `instance_seed/walmart_careers.db` is HF-managed and gitignored.** + `faf1c03a780314e71f9586d5b0edc69b` is the md5 to expect in the assets PR; the code + PR alone will not reproduce it without `scripts/fetch_assets.sh`. From 0068e144b7c2203976fae00f96bc42a13ba9dda1 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:58:35 -0500 Subject: [PATCH 07/15] fix(walmart_careers): salaried cards drop the ZIP and shift label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review fixes. 1. A salaried result card now shows title / "City, ST" / annual pay range only, matching the upstream results page for searchQuery=manager. Hourly cards are unchanged: "banner #store", "City, ST zip", "shift • $x - $y/hr". The card branches on job.population rather than on whether the store is an office. 2. Task 2's upstream_url pointed at the same posting as task 14's. It now points at https://careers.walmart.com/us/en/jobs/CP-9054-11013, the real upstream Freight Handler posting at store #9054 in Porterville, CA (harvested in scraped_data/recon_raw/results_gql_full.json). All 20 upstream_urls are now distinct. 3. VERIFICATION.md §9 records that upstream serves two salaried detail templates — the three-photo masthead captured in reference/job_detail_corp.png and a Workday-style variant with a white sticky bar and no masthead — and that the mirror implements the masthead variant. §7 and §9 are re-synced with the card change. Re-verified against a rebuilt image: /reset returns ready:true, the instance and instance_seed md5s both stay faf1c03a780314e71f9586d5b0edc69b, 20/20 task walkthroughs pass and the leak audit still reports 0 leaks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uso3TzMAGoE9CanJMW6wcJ --- sites/walmart_careers/VERIFICATION.md | 39 ++++++++++--------- sites/walmart_careers/tasks.jsonl | 2 +- .../walmart_careers/templates/_job_card.html | 23 +++++++---- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/sites/walmart_careers/VERIFICATION.md b/sites/walmart_careers/VERIFICATION.md index 4c6982cc..af4094cf 100644 --- a/sites/walmart_careers/VERIFICATION.md +++ b/sites/walmart_careers/VERIFICATION.md @@ -270,9 +270,9 @@ Fixed in this run: the `Open roles` tab only; `Add your location` is a link that opens a Location popover; `Filters` is a button that opens the facet panel; `Sort by: Relevance` is a dropdown. The permanently expanded sidebar panels are gone — the left column is the - map only. Cards are population-aware: salaried cards show title / `City, ST zip` / - `shift • $x - $y/yr`; hourly cards add the `banner #store` line above the city. Both - buttons are styled as the live outlined `Select +` pill. + map only. Cards are population-aware: a salaried card shows title / `City, ST` / + `$x - $y/yr` only; an hourly card adds the `banner #store` line, the ZIP and the + shift label. Both buttons are styled as the live outlined `Select +` pill. - **Job detail**: two layouts branched on `job.population`, both matching their reference screenshot — the three-photo masthead with the identity card (solid ld-blue for salaried, blue-over-navy for hourly), the left `Role Details` rail @@ -300,21 +300,16 @@ Remaining differences from `scraped_data/reference/*.png`: mirror exposes more facets at once (Brand, Shift, Employment Type + Rate, Career Area with nested categories); a single column would need scrolling to reach the career areas that tasks 5, 7, 18 and 19 depend on. -5. **The salaried card keeps the ZIP and the shift prefix** (`Sunnyvale, CA 94089-4731` - / `Multiple shifts • $143,000 - $286,000/yr`). The run instruction said "title / - City, ST / pay only", but `reference/home.png`'s trending cards show the ZIP and the - shift prefix on salaried cards, so the reference was followed. The banner line — the - part that genuinely differs between populations — is dropped for offices. -6. **The salaried detail page keeps the three-photo masthead.** The run instruction +5. **The salaried detail page keeps the three-photo masthead.** The run instruction described the salaried layout as "no hero photos", but `reference/job_detail_corp.png` shows the same three-photo masthead as the hourly page (with corporate photography rather than store photography), so the reference was followed. The two layouts still branch on `job.population` for the identity-card colour, the left rail's sub-items, the open-positions pill and the chip set. -7. **`About Us` links to a local `/about-us` page** assembled from the existing CMS +6. **`About Us` links to a local `/about-us` page** assembled from the existing CMS constants. The live nav item points at an off-domain corporate site, which is out of scope for an offline mirror. -8. Minor typography drift: the mirror uses the harvested `EverydaySansUI` variable +7. Minor typography drift: the mirror uses the harvested `EverydaySansUI` variable font, so line breaks inside long body paragraphs differ slightly from the reference captures. @@ -346,13 +341,14 @@ the two `SITES` lists. Nothing else hard-codes the port; the dev-only scripts un reset each). See §3 — the reviewer's verifiers should match the `applications` row, not assume a unique string. Flagged rather than changed because the brief fixed the numbering scheme. -2. **Two run-instruction deviations, both resolved in favour of the reference - screenshots**: salaried cards keep ZIP + shift prefix, and the salaried detail page - keeps its three-photo masthead (§7 items 5 and 6). Say the word and both flip to the - literal instruction. -3. **Task 2's `upstream_url`** points at `…/jobs/CP-9046-11101`, a real posting URL of - the right page type, but the same posting task 14 references. Harmless (the field is - documentation of the upstream page shape) but a reviewer may prefer a distinct URL. +2. **One run-instruction deviation remains**: the salaried detail page keeps its + three-photo masthead (§7 item 5), because `reference/job_detail_corp.png` shows one. + See item 7 below for why upstream can look either way. The salaried result card has + since been reverted to title / City, ST / pay only. +3. **Every `upstream_url` is now distinct.** Task 2 previously duplicated task 14's + posting URL; it now points at `…/jobs/CP-9054-11013`, the real upstream Freight + Handler posting at store #9054 in Porterville, CA (harvested in + `scraped_data/recon_raw/results_gql_full.json`). 4. **Location search accepts a whole state or territory** ("Puerto Rico", "PR", "Ohio") and then ignores the radius. The live site only geocodes cities/ZIPs. This was added so task 16 has a reliable route to every Puerto Rico posting; the radius route @@ -365,6 +361,11 @@ the two `SITES` lists. Nothing else hard-codes the port; the dev-only scripts un task 0 target is not rank 1 for `q=optician`. That shifts the seeded requisition IDs for the four Optician postings (the target is now `CP-5991-11240`). Any verifier drafted against an earlier build of this DB must be re-derived from the frozen seed. -7. **The mirror's `instance_seed/walmart_careers.db` is HF-managed and gitignored.** +7. **Upstream serves two salaried detail templates** — the three-photo masthead + captured in `reference/job_detail_corp.png` and a Workday-style variant with a white + sticky bar and no masthead. The mirror implements the masthead variant for both + populations, branching on `job.population` for the identity-card colour, the left + rail's sub-items, the open-positions pill and the chip set. +8. **The mirror's `instance_seed/walmart_careers.db` is HF-managed and gitignored.** `faf1c03a780314e71f9586d5b0edc69b` is the md5 to expect in the assets PR; the code PR alone will not reproduce it without `scripts/fetch_assets.sh`. diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl index 78850a98..d8978dce 100644 --- a/sites/walmart_careers/tasks.jsonl +++ b/sites/walmart_careers/tasks.jsonl @@ -1,6 +1,6 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--0", "ques": "Search for Optician roles and open the posting at the Neighborhood Market in Wichita, KS. Report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=optician"} {"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9054-11013"} {"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section, and how many open positions does the posting list?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} {"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} {"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} diff --git a/sites/walmart_careers/templates/_job_card.html b/sites/walmart_careers/templates/_job_card.html index 6eed4475..16b8cc08 100644 --- a/sites/walmart_careers/templates/_job_card.html +++ b/sites/walmart_careers/templates/_job_card.html @@ -1,8 +1,8 @@ {# - Result card. The lines mirror the live site: salaried postings sit at an office - and show only "City, ST zip", hourly postings show the "banner #store" line above - it. Everything else (street, requisition ID, open positions, shift window, - qualifications) is detail-page-only. + Result card. The lines mirror the live site: a salaried posting shows only its + title, "City, ST" and the annual pay range; an hourly posting adds the + "banner #store" line, the ZIP and the shift label. Everything else (street, + requisition ID, open positions, shift window, qualifications) is detail-only. #} {% macro job_card(job, saved_ids) -%} <article class="job-card"> @@ -12,12 +12,19 @@ <div class="body"> <h3><a href="{{ url_for('job_detail', job_id=job.job_id) }}">{{ job.title }}</a></h3> <div class="meta"> - {% if not job.store.is_office %} - <div>{{ job.store.banner }} #{{ job.store.store_number }}</div> + {% if job.is_salaried %} + <div>{{ job.store.city }}, {{ job.store.state }}</div> + {% else %} + {% if not job.store.is_office %} + <div>{{ job.store.banner }} #{{ job.store.store_number }}</div> + {% endif %} + <div>{{ job.store.city }}, {{ job.store.state }}  {{ job.store.zip }}</div> {% endif %} - <div>{{ job.store.city }}, {{ job.store.state }}  {{ job.store.zip }}</div> </div> - <div class="pay">{{ job.shift_label }} • {{ job.pay_range }}</div> + <div class="pay"> + {%- if not job.is_salaried %}{{ job.shift_label }} • {% endif -%} + {{ job.pay_range }} + </div> <div class="actions"> <a class="pill" href="{{ url_for('job_detail', job_id=job.job_id) }}">View role <span>+</span></a> {% if job.job_id in saved_ids %} From a9ab9905c7889903e8429723a59f6a8ff457d2fe Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:37:20 -0500 Subject: [PATCH 08/15] fix(walmart_careers): migrate to port 40019 (site 20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the upstream merge: every remaining reference to the old slot moves from 40017/41017 to 40019/41019. - tasks.jsonl: all 20 rows now carry "web": "http://localhost:40019/" - sites/walmart_careers/CLAUDE.md: port 40019, index 19 (the 20th and last site), alt-port 41019 - sites/walmart_careers/VERIFICATION.md: the alt-port docker run line, the port scan, the /health line and the port-reference table; the site count in that section goes from 18 to 20 and the README row from an 18- to a 20-mirror list phys_org keeps 40017 and target keeps 40018 — those are upstream's own slots. `grep -rn 40017 --exclude-dir=.git --exclude-dir=scraped_data .` now only matches sites/phys_org/. Re-verified on the 20-site image: all 20 ports 200, /health reports 20 sites all alive with walmart_careers on 40019, POST /reset/walmart_careers returns ready:true, the instance and instance_seed md5s both stay faf1c03a780314e71f9586d5b0edc69b before and after a docker restart, POST /reset-all resets all 20 sites in 1.71 s, the freezer re-runs _assert_distractors and reproduces the same seed byte for byte, the leak audit reports 0 leaks and all 20 task walkthroughs pass against port 41019. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uso3TzMAGoE9CanJMW6wcJ --- sites/walmart_careers/CLAUDE.md | 4 +-- sites/walmart_careers/VERIFICATION.md | 38 ++++++++++++------------- sites/walmart_careers/tasks.jsonl | 40 +++++++++++++-------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/sites/walmart_careers/CLAUDE.md b/sites/walmart_careers/CLAUDE.md index b3a126a3..48474ee6 100644 --- a/sites/walmart_careers/CLAUDE.md +++ b/sites/walmart_careers/CLAUDE.md @@ -1,7 +1,7 @@ # walmart_careers — site notes -Mirror of https://careers.walmart.com. Port **40017** (index 17 in `websyn_start.sh`); -alt-port test container maps it to **41017**. +Mirror of https://careers.walmart.com. Port **40019** (index 19 in `websyn_start.sh`, the 20th and last site); +alt-port test container maps it to **41019**. ## Layout diff --git a/sites/walmart_careers/VERIFICATION.md b/sites/walmart_careers/VERIFICATION.md index af4094cf..0bca7dfe 100644 --- a/sites/walmart_careers/VERIFICATION.md +++ b/sites/walmart_careers/VERIFICATION.md @@ -4,7 +4,7 @@ Everything below was executed against this working tree on 2026-09-06. Commands say "container" ran against `webharbor:dev` started as ```bash -docker run -d --rm --name wh-test -p 8201:8101 -p 41000-41017:40000-40017 webharbor:dev +docker run -d --rm --name wh-test -p 8201:8101 -p 41000-41019:40000-40019 webharbor:dev ``` The dev-only drivers used here live in `sites/walmart_careers/scripts_dev/` @@ -55,13 +55,13 @@ Both md5s match in both directions. Bootstrap seeding is gated per whole functio (`seed_database()` on `Job.query.count() > 0`, `seed_benchmark_users()` on the presence of `alice.j@test.com`), so a populated DB triggers no commit at all. -### All 18 sites still serve +### All 20 sites still serve ``` -$ for p in $(seq 41000 41017); do curl -so /dev/null -w "$p %{http_code}\n" http://localhost:$p/; done -41000 200 … 41017 200 # all eighteen returned 200 -$ curl -s http://localhost:8201/health # 18 sites, alive: true for every one -$ curl -s http://localhost:41017/_health +$ for p in $(seq 41000 41019); do curl -so /dev/null -w "$p %{http_code}\n" http://localhost:$p/; done +41000 200 … 41019 200 # all twenty returned 200 +$ curl -s http://localhost:8201/health # 20 sites, alive: true for every one +$ curl -s http://localhost:41019/_health {"areas":7,"categories":33,"jobs":200,"ok":true,"site":"walmart_careers","stores":44,"users":4} ``` @@ -104,7 +104,7 @@ submissions). Run against the container with a control-plane reset before each t so every task starts from the frozen seed state: ``` -$ python scripts_dev/walkthrough.py http://localhost:41017 \ +$ python scripts_dev/walkthrough.py http://localhost:41019 \ http://localhost:8201/reset/walmart_careers ``` @@ -315,21 +315,21 @@ Remaining differences from `scraped_data/reference/*.png`: --- -## 8. Files that reference port 40017 +## 8. Files that reference port 40019 | file | reference | |---|---| -| `websyn_start.sh` | `walmart_careers` is index 17 of `SITES=( … )` → 40000 + 17 | -| `control_server.py` | `'walmart_careers'` is the 18th entry of `SITES` (same order) | -| `Dockerfile` | `EXPOSE 8101 40000-40017` | -| `sites/walmart_careers/tasks.jsonl` | `"web": "http://localhost:40017/"` on all 20 rows | -| `sites/walmart_careers/CLAUDE.md` | "Port **40017** … alt-port **41017**" | -| `README.md` | `-p 40000-40017:40000-40017`, and the 18-mirror list | -| `AGENTS.md` | three `40000-40017` occurrences plus `41000-41017` in the pre-PR block | -| `CONTRIBUTING.md` | TL;DR `-p 40000-40017:40000-40017` | -| `CLAUDE.md` | `:40000-40017` / `:41000-41017` in "Existing containers" | - -Reassigning the slot is a single `sed` over `40017`/`41017` plus moving the entry in +| `websyn_start.sh` | `walmart_careers` is index 19 of `SITES=( … )` → 40000 + 19 | +| `control_server.py` | `'walmart_careers'` is the 20th entry of `SITES` (same order) | +| `Dockerfile` | `EXPOSE 8101 40000-40019` | +| `sites/walmart_careers/tasks.jsonl` | `"web": "http://localhost:40019/"` on all 20 rows | +| `sites/walmart_careers/CLAUDE.md` | "Port **40019** … alt-port **41019**" | +| `README.md` | `-p 40000-40019:40000-40019`, and the 20-mirror list | +| `AGENTS.md` | three `40000-40019` occurrences plus `41000-41019` in the pre-PR block | +| `CONTRIBUTING.md` | TL;DR `-p 40000-40019:40000-40019` | +| `CLAUDE.md` | `:40000-40019` / `:41000-41019` in "Existing containers" | + +Reassigning the slot is a single `sed` over `40019`/`41019` plus moving the entry in the two `SITES` lists. Nothing else hard-codes the port; the dev-only scripts under `scripts_dev/` take the base URL as an argument. diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl index d8978dce..a69b1272 100644 --- a/sites/walmart_careers/tasks.jsonl +++ b/sites/walmart_careers/tasks.jsonl @@ -1,20 +1,20 @@ -{"web_name": "Walmart Careers", "id": "Walmart Careers--0", "ques": "Search for Optician roles and open the posting at the Neighborhood Market in Wichita, KS. Report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=optician"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9054-11013"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section, and how many open positions does the posting list?", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--6", "ques": "Set your location to Cleveland, OH within 25 miles, filter to Full time roles on a Weekday Day shift, and open the Online Order Filling Team Supervisor posting. Report the street address and the number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and the street address listed for its location.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--8", "ques": "There are Auto Care Center Technician postings at two Mississippi stores. Open both and report which store number has more open positions and how many.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=auto+care+center+technician"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--9", "ques": "Two Freight Handler postings are located in Marcy, NY at different facilities. Which one has the earlier shift start time? Report its requisition ID and that start window.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=freight+handler"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--10", "ques": "Compare the Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery) postings in Bentonville, AR and Hoboken, NJ. Which one requires more years of experience under \"Option 2\" of its Minimum Qualifications? Report that posting's requisition ID and the number of years.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2435546"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--11", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), search for Yard Driver roles and save the Williamsburg, VA posting to your Saved roles.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--19", "ques": "Log in as bob.c@test.com (password: TestPass123!). From the Stores and Clubs career area page, open the Digital Pickup and Delivery category, filter it to Full time roles, and open the posting with the fewest open positions. Save that role to your Saved roles, then report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40017/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/stores-and-clubs"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--0", "ques": "Search for Optician roles and open the posting at the Neighborhood Market in Wichita, KS. Report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=optician"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9054-11013"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section, and how many open positions does the posting list?", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--6", "ques": "Set your location to Cleveland, OH within 25 miles, filter to Full time roles on a Weekday Day shift, and open the Online Order Filling Team Supervisor posting. Report the street address and the number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and the street address listed for its location.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--8", "ques": "There are Auto Care Center Technician postings at two Mississippi stores. Open both and report which store number has more open positions and how many.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=auto+care+center+technician"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--9", "ques": "Two Freight Handler postings are located in Marcy, NY at different facilities. Which one has the earlier shift start time? Report its requisition ID and that start window.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=freight+handler"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--10", "ques": "Compare the Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery) postings in Bentonville, AR and Hoboken, NJ. Which one requires more years of experience under \"Option 2\" of its Minimum Qualifications? Report that posting's requisition ID and the number of years.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2435546"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--11", "ques": "Log in with the demo account (email: alice.j@test.com, password: TestPass123!), search for Yard Driver roles and save the Williamsburg, VA posting to your Saved roles.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--19", "ques": "Log in as bob.c@test.com (password: TestPass123!). From the Stores and Clubs career area page, open the Digital Pickup and Delivery category, filter it to Full time roles, and open the posting with the fewest open positions. Save that role to your Saved roles, then report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/stores-and-clubs"} From 900d24b1c0af0aecbbab639051291fed5759708b Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 09:17:41 -0500 Subject: [PATCH 09/15] fix(walmart_careers): move ground truth out of the shipped tree; rebalance filter coverage; store hub content in the DB Compliance audit follow-up. Ground truth out of the shipped tree - VERIFICATION.md moves to scripts_dev/ (git- and docker-ignored) and is untracked; `git ls-files sites/walmart_careers | grep -i verification` is empty. - TASK_TARGETS, _assert_distractors() and _years_in() move out of seed_data.py into scripts_dev/assert_distractors.py, which exposes assert_distractors(). build_seed_database() loads it by path only when the file exists, so the freezer still enforces every invariant locally while a tree without it builds the byte-identical database and says the checks were skipped. - CLAUDE.md now describes structure and conventions only. Catalog rebalancing (200 -> 223 jobs) - Vizio 6 -> 20, Intern 12 -> 21, Students 12 -> 21, so every exposed brand, employment-type, rate, shift and career-area filter value returns >= 20 roles. Added placements reuse existing title families: 14 salaried roles at the VIZIO Irvine campus and 9 more internships. The Students/Intern/Sam's Club merchandising internship stays unique and every other task target is still unique; 20/20 tasks still walk through. Hub copy and trending roles in the database - New Store.hub_name / hub_blurb / hub_image and Job.is_trending, seeded from catalog_source.HUB_COPY and TRENDING_JOB_IDS; locations.html, area.html and trending_jobs() read the columns. _content.py keeps only static chrome. - The three trending postings pin their job_id so the list cannot drift. Also adds static/js/.gitkeep. Verified on a fresh build (-p 8201:8101 -p 41000-41019:40000-40019): all 20 ports 200, /health alive on all 20, POST /reset/walmart_careers ready:true, instance == instance_seed md5 918eca54a717c180f284ede81f88cab7 before and after docker restart, reset-all 20/20 ready in ~1.6s, leak audit 0 leaks across all 20 tasks, 20/20 task walkthrough, 26/26 robustness checks, and two PYTHONHASHSEED=0 freezer runs producing identical md5s with the distractor assertions clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8BbRAQLRFrPW1EtkPFNp3 --- sites/walmart_careers/CLAUDE.md | 36 +- sites/walmart_careers/VERIFICATION.md | 371 ------------------ sites/walmart_careers/_content.py | 35 -- sites/walmart_careers/app.py | 9 +- sites/walmart_careers/catalog_source.py | 93 ++++- sites/walmart_careers/seed_data.py | 362 ++--------------- sites/walmart_careers/static/js/.gitkeep | 0 sites/walmart_careers/templates/area.html | 9 +- .../walmart_careers/templates/locations.html | 9 +- 9 files changed, 155 insertions(+), 769 deletions(-) delete mode 100644 sites/walmart_careers/VERIFICATION.md create mode 100644 sites/walmart_careers/static/js/.gitkeep diff --git a/sites/walmart_careers/CLAUDE.md b/sites/walmart_careers/CLAUDE.md index 48474ee6..5a51bbfd 100644 --- a/sites/walmart_careers/CLAUDE.md +++ b/sites/walmart_careers/CLAUDE.md @@ -8,12 +8,16 @@ alt-port test container maps it to **41019**. | file | role | |---|---| | `app.py` | models, routes, scored search, deterministic SVG maps, bootstrap | -| `catalog_source.py` | the source catalog: areas, categories, 44 stores, 37 hourly + 29 salaried title families with explicit placements | +| `catalog_source.py` | the source catalog: areas, categories, 44 stores, 37 hourly + 29 salaried title families with explicit placements, hub copy, trending job ids | | `seed_data.py` | turns the catalog into SQLite; `build_seed_database()` is the freezer | -| `_content.py` | CMS-style prose, design constants, US/PR map outlines | +| `_content.py` | static chrome strings only: headings, boilerplate prose, design constants, US/PR map outlines | | `templates/` | 19 Jinja2 templates + `_job_card.html` macro | -| `scripts_dev/` | local-only helpers; gitignored and dockerignored | -| `VERIFICATION.md` | what was verified, how, and what still needs human judgment | +| `static/` | `css/`, `js/`, `icons/`, `fonts/` in git; `images/` HF-managed | +| `scripts_dev/` | local-only helpers and build-time invariants; gitignored and dockerignored | + +Everything a handler renders about a job, a store or a hub comes from SQLAlchemy. +`_content.py` holds no per-record content: hub name/blurb/image live on `Store`, +and the trending flag lives on `Job.is_trending`. ## Rebuilding the seed DB @@ -22,13 +26,16 @@ cd sites/walmart_careers PYTHONHASHSEED=0 python seed_data.py # writes instance_seed/walmart_careers.db ``` -Run it twice and compare md5s — the build is byte-reproducible. `build_seed_database()` -also runs `_assert_distractors()`, which fails the build if a catalog edit breaks a -volume invariant (jobs per category/store/state/shift) or a task's near-miss set. -`_assert_distractors()` never runs at import or at `/reset` time. It covers all 20 -tasks: `TASK_TARGETS` proves each task's locator (title + city, or title + store) -resolves to exactly one posting, and each task block checks its result set has ≥6 rows -with ≤50 % of them satisfying every constraint the task states. +Run it twice and compare md5s — the build is byte-reproducible. + +`build_seed_database()` also runs the build-time invariant checks, which fail the +build if a catalog edit breaks a volume invariant (jobs per category/store/state/ +shift) or a benchmark task's near-miss set. Those checks live in +`scripts_dev/assert_distractors.py`, which the freezer loads by path *only if the +file exists* — it is git-ignored and docker-ignored, so it never reaches the shipped +tree, and a checkout without it builds the identical database and prints a note that +the checks were skipped. Nothing in `seed_data.py` or `app.py` encodes what a task +is looking for. The checks never run at import, bootstrap or `/reset` time. ## Determinism rules that must hold @@ -48,8 +55,11 @@ python scripts_dev/robustness.py <base_url> python scripts_dev/shots.py <base_url> <out_dir> # 1440px screenshots ``` -`walkthrough.py`, `leak_audit.py` and `robustness.py` hold the ground-truth answers, -which is exactly why `scripts_dev/` is in both `.gitignore` and `.dockerignore`. +`assert_distractors.py` is not run directly — the freezer imports it by path. + +`assert_distractors.py`, `walkthrough.py`, `leak_audit.py`, `robustness.py` and +`VERIFICATION.md` hold the ground-truth answers, which is exactly why `scripts_dev/` +is in both `.gitignore` and `.dockerignore`. ## Assets diff --git a/sites/walmart_careers/VERIFICATION.md b/sites/walmart_careers/VERIFICATION.md deleted file mode 100644 index 0bca7dfe..00000000 --- a/sites/walmart_careers/VERIFICATION.md +++ /dev/null @@ -1,371 +0,0 @@ -# walmart_careers — verification record - -Everything below was executed against this working tree on 2026-09-06. Commands that -say "container" ran against `webharbor:dev` started as - -```bash -docker run -d --rm --name wh-test -p 8201:8101 -p 41000-41019:40000-40019 webharbor:dev -``` - -The dev-only drivers used here live in `sites/walmart_careers/scripts_dev/` -(`walkthrough.py`, `leak_audit.py`, `robustness.py`, `shots.py`, `serve.py`). -That directory is gitignored **and** dockerignored, so the answer keys those -scripts contain never ship with the site or the image. - ---- - -## 1. Byte-identical reset - -### Seed build reproducibility (two consecutive freezer runs) - -``` -$ PYTHONHASHSEED=0 python seed_data.py # run 1 -$ md5 -q instance_seed/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b -$ PYTHONHASHSEED=0 python seed_data.py # run 2 -$ md5 -q instance_seed/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b -``` - -### Container: after `POST /reset/walmart_careers` - -``` -$ curl -X POST http://localhost:8201/reset/walmart_careers -{"pid":2506,"ready":true,"site":"walmart_careers"} - -$ docker exec wh-test md5sum \ - /opt/WebSyn/walmart_careers/instance/walmart_careers.db \ - /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db -``` - -### Container: after `docker restart wh-test` - -``` -$ docker restart wh-test -$ docker exec wh-test md5sum \ - /opt/WebSyn/walmart_careers/instance/walmart_careers.db \ - /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance/walmart_careers.db -faf1c03a780314e71f9586d5b0edc69b /opt/WebSyn/walmart_careers/instance_seed/walmart_careers.db -``` - -Both md5s match in both directions. Bootstrap seeding is gated per whole function -(`seed_database()` on `Job.query.count() > 0`, `seed_benchmark_users()` on the -presence of `alice.j@test.com`), so a populated DB triggers no commit at all. - -### All 20 sites still serve - -``` -$ for p in $(seq 41000 41019); do curl -so /dev/null -w "$p %{http_code}\n" http://localhost:$p/; done -41000 200 … 41019 200 # all twenty returned 200 -$ curl -s http://localhost:8201/health # 20 sites, alive: true for every one -$ curl -s http://localhost:41019/_health -{"areas":7,"categories":33,"jobs":200,"ok":true,"site":"walmart_careers","stores":44,"users":4} -``` - ---- - -## 2. Seeded rows per model - -| model | rows | -|---|---| -| `Area` | 7 (6 filterable career areas + Military) | -| `Category` | 33 | -| `Store` | 44 (6 offices + 38 field locations, 13 states/territories) | -| `Job` | 200 | -| `User` | 4 benchmark users, password `TestPass123!` | -| `SavedJob` | 10 (alice 3, bob 3, carol 2, david 2) | -| `Application` | 4 (`WMC-000001` … `WMC-000004`) | - -Job distribution: - -| dimension | breakdown | -|---|---| -| population | hourly 140 / salaried 60 | -| brand | Walmart 160 / Sam's Club 34 / Vizio 6 | -| employment type | Full time 120 / Part time 68 / Intern 12 | -| career area | Stores and Clubs 72, Supply Chain and Transportation 42, Technology 30, Corporate 24, Healthcare 20, Students 12 | -| distinct titles | 66 | -| shifts | every one of the 7 values appears on ≥24 postings | - -`_assert_distractors()` (freezer-only) also enforces: ≥4 jobs per category, ≥3 per -store, ≥8 per state, unique hashtags per hourly title family, unique minimum -qualification texts per salaried posting, and no trending role that is a task target. - ---- - -## 3. Task walkthroughs — 20/20 - -`scripts_dev/walkthrough.py` drives every task through Chromium (header search box, -Filters popover checkboxes, the Location popover, real login/apply/save/unsave form -submissions). Run against the container with a control-plane reset before each task, -so every task starts from the frozen seed state: - -``` -$ python scripts_dev/walkthrough.py http://localhost:41019 \ - http://localhost:8201/reset/walmart_careers -``` - -| task | what the walkthrough read off the page | -|---|---| -| 0 | `CP-5991-11240` / `2441 S Rock Rd` | -| 1 | `Option 2: 6 years' experience in software engineering or related area.` | -| 2 | `Shift may start between 6:00pm - 3:00am` / 3 open positions | -| 3 | `#pharmacytechjobs` / 2 open positions | -| 4 | `CP-4750-11184` / 3 open positions | -| 5 | `R-2411668` / Option 1 degree field | -| 6 | `10000 Brookpark Rd` / 2 open positions | -| 7 | `Intern (Fixed Term)` / `2101 SE Simple Savings Dr` | -| 8 | store `#1230` with 5 open positions | -| 9 | `CP-6038-10642` / `Shift may start between 3:00pm - 7:30pm` | -| 10 | `R-2451180` / 7 years | -| 11 | `CP-6088-10488` saved to alice's saved roles | -| 12 | the Neighborhood Market saved role removed from bob's list | -| 13 | `WMC-000005` | -| 14 | new account registered + `CP-9046-11101` saved | -| 15 | david's phone/city updated and persisted | -| 16 | `CP-2503-10726` / 5 open positions | -| 17 | `Shift may start between 6:00am - 11:00am` / `WMC-000005` | -| 18 | `CP-6014-12091` / `Shift may start between 5:00am - 10:00am` / 4 open positions | -| 19 | `CP-1179-11268` / `1301 SW Wanamaker Rd` | - -**Note for the reviewer — confirmation numbers collide across tasks 13 and 17.** -The seed holds four applications, so the first application submitted against a freshly -reset database is always id 5 → `WMC-000005`. Tasks 13 and 17 therefore both produce -`WMC-000005` when each is run from a fresh reset (as above). This is by design: the -numbering scheme is `"WMC-" + zero-padded application id` and was left unchanged. A -verifier must not treat the confirmation number as a task-unique value — it should -match the `applications` row by `job_id` + `email` and then compare -`confirmation_no`. If both tasks run in the same session without a reset in between, -task 17 yields `WMC-000006`. - ---- - -## 4. Answer-leak audit (task × page matrix) - -Produced by `scripts_dev/leak_audit.py` against the **running container's rendered -HTML** (raw HTML, so `display:none` blocks and HTML comments are included). -`clean` = the answer token appears nowhere in the page. `n/a (stateful)` = the task's -deliverable is a database change, not a string read off a page. - -Requisition IDs are part of every posting URL by design; `href`, `action` and hidden -`value` attributes are therefore stripped before a token is judged readable, which is -exactly why no task in this set answers with a requisition ID alone. - -| task | home | results (natural query) | career area page | /resources/location | results URL audited | -|---|---|---|---|---|---| -| 0 | clean | clean | clean | clean | /results?q=optician | -| 1 | clean | clean | clean | clean | /results?q=staff+software+engineer | -| 2 | clean | clean | clean | clean | /results?q=freight+handler | -| 3 | clean | clean | clean | clean | /results?q=pharmacy+technician | -| 4 | clean | clean | clean | clean | /results?brand=Sam's+Club&type=Part+time&shift=Weekend+Overnight | -| 5 | clean | clean | clean | clean | /results?area=technology&type=Full+time&loc=Hoboken,+NJ&radius=25 | -| 6 | clean | clean | clean | clean | /results?loc=Cleveland,+OH&radius=25&type=Full+time&shift=Weekday+Day | -| 7 | clean | clean | clean | clean | /results?area=students&type=Intern&brand=Sam's+Club | -| 8 | clean | clean | clean | clean | /results?q=auto+care+center+technician | -| 9 | clean | clean | clean | clean | /results?q=freight+handler | -| 10 | clean | clean | clean | clean | /results?q=delivery+search+arrival+matching | -| 11 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=yard+driver | -| 12 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=asset+protection | -| 13 | clean | clean | clean | clean | /results?q=pharmacy+technician | -| 14 | n/a (stateful) | n/a (stateful) | n/a (stateful) | n/a (stateful) | /results?q=ecom+warehouse+worker | -| 15 | clean | clean | clean | clean | /results?q= | -| 16 | clean | clean | clean | clean | /results?q=cashier&loc=Puerto+Rico | -| 17 | clean | clean | clean | clean | /candidate-home/saved-roles | -| 18 | clean | clean | clean | clean | /results?area=supply-chain-and-transportation&category=drivers | -| 19 | clean | clean | clean | clean | /results?area=stores-and-clubs&category=digital-pickup-and-delivery&type=Full+time | - -**0 leaks.** Tokens audited are the detail-only fields each task asks for: street -addresses, open-position counts, shift start windows, hashtags, worker-type chips, -qualification texts, years of experience and confirmation numbers. - -### The 13 leak archetypes - -| # | archetype | status | -|---|---|---| -| 1 | numeric difference pre-computed | tasks 8/10/18/19 make the agent open both postings and compare; no page states the delta | -| 2 | count the agent should count | task 16 requires visiting each PR cashier posting; the results heading counts roles, never open positions | -| 3 | verbatim task framing echoed | the results `<h1>`/`<title>` are `N open roles` — the query is no longer echoed | -| 4 | pre-bundled answer sentence | body copy is generated from per-family templates with store/shift/pay slots; no sentence restates a task answer | -| 5 | pinned/highlighted answer callout | no callouts; the fact column is identical for every posting | -| 6 | spoon-fed list endings with count | the "What you'll bring" list has no trailing count | -| 7 | wiki paragraph matching the question | n/a — no article pages | -| 8 | operand-only fuzzy match in the backend | search scores over title+category+area+banner+city+state+brand; `description` is deliberately excluded from the blob | -| 9 | bare-anchor → answer-bucket flood | the map is a deterministic SVG of the current result set; it carries city names and counts, never postings | -| 10 | algorithm-revealing UI text | the sort control says "Relevance"/"Most recent" only | -| 11 | sort order putting the answer first | the freezer asserts the target is not rank 1 for its natural query (tasks 0/2/13); relevance ties break on a seeded shuffle | -| 12 | pre-curated lookup table | none; every fact comes from SQLAlchemy | -| 13 | constraint values in item names | titles carry no shift, state, brand or pay words; the freezer's per-task locator check keeps each title+city pair unique | - ---- - -## 5. Near-miss distractors and catalog breadth - -`_assert_distractors()` enforces, per task, ≥6 results on the task's natural query or -facet set with ≤50 % of them satisfying *every* stated constraint. Measured on the -frozen seed: - -| task | result set | size | full matches | -|---|---|---|---| -| 0 | `q=optician` | 6 | 1 | -| 2 | `q=freight handler` | 6 | 1 | -| 3 | Pharmacy Services category | 6 | 1 | -| 4 | Sam's Club · Part time · Weekend Overnight | 8 | 1 (3 are ≤ $20/hr, only one of those is in TX) | -| 5 | Full time Technology within 25 mi of Hoboken | 6 | 1 | -| 6 | within 25 mi of Cleveland, OH | 8 (4 after Full time + Weekday Day) | 1 | -| 7 | Students · Intern | 12 | 1 | -| 11 | `q=yard driver` | 8 | 1 | -| 13 | `q=pharmacy technician` | 60 | 1 | -| 14 | `q=ecom warehouse worker` | 19 | 1 | -| 16 | PR hourly cashier postings | 5 (4 list Weekday Day) | 1 | -| 18 | Drivers category | 8 | 2 | -| 19 | Digital Pickup and Delivery | 8 (4 Full time) | 1 | - -Deliberate near-misses: Ponce PR has the second-highest open-position count among PR -cashiers but does not list Weekday Day (task 16); the Plano TX Sam's Club has two Part -time Weekend Overnight postings and only one is at or under $20/hr (task 4); Marcy NY -carries two Freight Handler postings at different facilities with different windows -(task 9); the second Merchandising Intern sits at the Walmart home office rather than -the Sam's Club one (task 7). - ---- - -## 6. Interaction robustness — 26/26 - -`scripts_dev/robustness.py`, against the container: - -- partial and loose queries return the right family (`cashi`, `freight hand`, - `optical` → Optician, `sams club`, `truck driver`) -- an unresolvable location renders "We couldn't find that location", not an empty page -- a wrong password is rejected; `/account`, `/account/edit` and - `/candidate-home/applications` redirect to `/login`; `/candidate-home/saved-roles` - renders logged out with a sign-in CTA -- the apply form validates client-side *and* server-side (a raw POST with an empty - body and one with a malformed email are both rejected with field errors) -- registration rejects a duplicate email, a short password and a password mismatch -- an anonymous save redirects to `/login?next=`; a signed-in save and unsave both - persist across a reload -- a POST without a CSRF token returns 400 -- an unknown job id renders the 404 page - ---- - -## 7. Visual fidelity - -Screenshots at 1440 px are in `scraped_data/mirror/`, using the same filenames as -`scraped_data/reference/` (`scripts_dev/shots.py`; `scraped_data/` is gitignored, so -these are local review artefacts). - -Fixed in this run: - -- **Header** now mirrors the live bar exactly: the full spark + `<>` + "Careers" - lockup, then `Career areas` (dropdown listing all six areas), `Brands`, `Resources`, - `About Us`, `Military`, a white search pill with a blue circular search button, and a - user icon whose popover holds *My account / Saved roles / Login/Signup / EN* (or the - initials avatar plus *My applications / Log out* when signed in). Nothing wraps at - 1440 px; the header spans the full viewport width like the original. -- **Results page**: heading is `N open roles` with no query echo; the count badge is on - the `Open roles` tab only; `Add your location` is a link that opens a Location - popover; `Filters` is a button that opens the facet panel; `Sort by: Relevance` is a - dropdown. The permanently expanded sidebar panels are gone — the left column is the - map only. Cards are population-aware: a salaried card shows title / `City, ST` / - `$x - $y/yr` only; an hourly card adds the `banner #store` line, the ZIP and the - shift label. Both buttons are styled as the live outlined `Select +` pill. -- **Job detail**: two layouts branched on `job.population`, both matching their - reference screenshot — the three-photo masthead with the identity card (solid - ld-blue for salaried, blue-over-navy for hourly), the left `Role Details` rail - (sub-items only for hourly), the title, the address block with the map card, and the - three dark navy chips (pay / worker type / Salaried for salaried; pay / employment - type / shift window for hourly). `Apply now` is ld-blue on both. -- **Home**: centred hero with the inset dark search pill and circular button, plus the - three-photo strip that overlaps the blue/white boundary; trending roles are three - across on white. - -Remaining differences from `scraped_data/reference/*.png`: - -1. **The maps are deterministic SVGs, not Google Maps.** The results cluster map is a - stylised US+PR outline with bubbles positioned from the seeded store coordinates; - the detail page shows an SVG map card with a pin instead of a Google tile. This was - the explicit decision in the build brief (PLAN.md §7.9 rejected). The mirror map is - also less zoomed than the reference, which frames the whole western hemisphere. -2. **No `Chat` accordion and no `Go back` link** in the results sidebar. The chat panel - is the site's LLM search assistant, which the mirror deliberately does not - reproduce; `Go back` is a browser-history control with no server-side meaning. -3. **`Future roles` and `Content` tabs carry no count badge** and open an explicit - empty-state panel. The brief asked for a badge on `Open roles` only; the live site - shows counts on all three. -4. **The Filters popover is a four-column panel**, not the live single column. The - mirror exposes more facets at once (Brand, Shift, Employment Type + Rate, Career - Area with nested categories); a single column would need scrolling to reach the - career areas that tasks 5, 7, 18 and 19 depend on. -5. **The salaried detail page keeps the three-photo masthead.** The run instruction - described the salaried layout as "no hero photos", but `reference/job_detail_corp.png` - shows the same three-photo masthead as the hourly page (with corporate photography - rather than store photography), so the reference was followed. The two layouts still - branch on `job.population` for the identity-card colour, the left rail's sub-items, - the open-positions pill and the chip set. -6. **`About Us` links to a local `/about-us` page** assembled from the existing CMS - constants. The live nav item points at an off-domain corporate site, which is out of - scope for an offline mirror. -7. Minor typography drift: the mirror uses the harvested `EverydaySansUI` variable - font, so line breaks inside long body paragraphs differ slightly from the reference - captures. - ---- - -## 8. Files that reference port 40019 - -| file | reference | -|---|---| -| `websyn_start.sh` | `walmart_careers` is index 19 of `SITES=( … )` → 40000 + 19 | -| `control_server.py` | `'walmart_careers'` is the 20th entry of `SITES` (same order) | -| `Dockerfile` | `EXPOSE 8101 40000-40019` | -| `sites/walmart_careers/tasks.jsonl` | `"web": "http://localhost:40019/"` on all 20 rows | -| `sites/walmart_careers/CLAUDE.md` | "Port **40019** … alt-port **41019**" | -| `README.md` | `-p 40000-40019:40000-40019`, and the 20-mirror list | -| `AGENTS.md` | three `40000-40019` occurrences plus `41000-41019` in the pre-PR block | -| `CONTRIBUTING.md` | TL;DR `-p 40000-40019:40000-40019` | -| `CLAUDE.md` | `:40000-40019` / `:41000-41019` in "Existing containers" | - -Reassigning the slot is a single `sed` over `40019`/`41019` plus moving the entry in -the two `SITES` lists. Nothing else hard-codes the port; the dev-only scripts under -`scripts_dev/` take the base URL as an argument. - ---- - -## 9. Needs human judgment - -1. **Confirmation numbers repeat across tasks 13 and 17** (`WMC-000005` from a fresh - reset each). See §3 — the reviewer's verifiers should match the `applications` row, - not assume a unique string. Flagged rather than changed because the brief fixed the - numbering scheme. -2. **One run-instruction deviation remains**: the salaried detail page keeps its - three-photo masthead (§7 item 5), because `reference/job_detail_corp.png` shows one. - See item 7 below for why upstream can look either way. The salaried result card has - since been reverted to title / City, ST / pay only. -3. **Every `upstream_url` is now distinct.** Task 2 previously duplicated task 14's - posting URL; it now points at `…/jobs/CP-9054-11013`, the real upstream Freight - Handler posting at store #9054 in Porterville, CA (harvested in - `scraped_data/recon_raw/results_gql_full.json`). -4. **Location search accepts a whole state or territory** ("Puerto Rico", "PR", "Ohio") - and then ignores the radius. The live site only geocodes cities/ZIPs. This was added - so task 16 has a reliable route to every Puerto Rico posting; the radius route - (`loc=Bayamon, PR`, 60 miles) also reaches all five and is asserted at build time. - Drop it if the reviewer considers it too much of a mirror-only affordance. -5. **`Students` has no career-area index page** (`has_index_page = False`, matching the - live site), so the "Career areas" menu sends it to `/results?area=students`. Task 7 - reaches it through the Filters panel, which is what its wording asks for. -6. **The Optician family's placement order was reordered** in `catalog_source.py` so the - task 0 target is not rank 1 for `q=optician`. That shifts the seeded requisition IDs - for the four Optician postings (the target is now `CP-5991-11240`). Any verifier - drafted against an earlier build of this DB must be re-derived from the frozen seed. -7. **Upstream serves two salaried detail templates** — the three-photo masthead - captured in `reference/job_detail_corp.png` and a Workday-style variant with a white - sticky bar and no masthead. The mirror implements the masthead variant for both - populations, branching on `job.population` for the identity-card colour, the left - rail's sub-items, the open-positions pill and the chip set. -8. **The mirror's `instance_seed/walmart_careers.db` is HF-managed and gitignored.** - `faf1c03a780314e71f9586d5b0edc69b` is the md5 to expect in the assets PR; the code - PR alone will not reproduce it without `scripts/fetch_assets.sh`. diff --git a/sites/walmart_careers/_content.py b/sites/walmart_careers/_content.py index d22401ff..db7a8fb5 100644 --- a/sites/walmart_careers/_content.py +++ b/sites/walmart_careers/_content.py @@ -15,15 +15,6 @@ SITE_NAME = "Walmart Careers" COPYRIGHT = "©2026 Walmart Inc." -# Job ids surfaced as "Trending roles" on the home page, the hiring page and the -# logged-out saved-roles page. None of them is the target of a benchmark task — -# a trending card would otherwise hand an agent the target without a search. -TRENDING_JOB_IDS = [ - "R-2414279", - "R-2413636", - "CP-1236-10888", -] - HERO_HEADLINE_1 = "Cashiers wanted." HERO_HEADLINE_2 = "Next move, yours." SEARCH_PLACEHOLDER = "Search by team, department, keyword" @@ -172,32 +163,6 @@ def benefit_tiles_for(brand: str, population: str) -> list[tuple[str, str, str, "Our hubs spark collaboration and innovation, so you're free to energize and push boundaries " "from the space that serves you best." ) -HUB_COPY = { - "10101": ( - "Northwest Arkansas", - "Northwest Arkansas offers trails, local eats, and the Crystal Bridges Museum - while our " - "12 new Home Office buildings reflect the company's story through thoughtful design.", - "loc-nwa.jpg", - ), - "11807": ( - "Sunnyvale", - "A weekend hike through the mountains. An evening walk next to the ocean. A quick visit to a " - "museum. The best of both worlds - work and leisure - are waiting for you right here.", - "loc-sunnyvale.jpg", - ), - "11003": ( - "Hoboken", - "Just across from Lower Manhattan, Hoboken is a walkable, character-filled town on the Hudson " - "with a truly unique charm.", - "loc-hoboken.jpg", - ), - "11500": ( - "Dallas", - "Our Dallas office anchors merchandising, finance and supply chain teams in the middle of one " - "of the fastest-growing metros in the country.", - "loc-dc-metro.jpg", - ), -} LOCATIONS_CLOSING = ( "Between making an impact at scale and our culture of promoting from within, from coders all the " "way to cashiers, Walmart is the best place to build a career, period." diff --git a/sites/walmart_careers/app.py b/sites/walmart_careers/app.py index a1bd1426..2d3572bf 100644 --- a/sites/walmart_careers/app.py +++ b/sites/walmart_careers/app.py @@ -136,6 +136,9 @@ class Store(db.Model): lng = db.Column(db.Float, nullable=False) is_hub = db.Column(db.Boolean, nullable=False, default=False) is_office = db.Column(db.Boolean, nullable=False, default=False) + hub_name = db.Column(db.String(80), nullable=True) + hub_blurb = db.Column(db.Text, nullable=True) + hub_image = db.Column(db.String(80), nullable=True) @property def banner_line(self) -> str: @@ -162,6 +165,7 @@ class Job(db.Model): max_pay = db.Column(db.Numeric(10, 2), nullable=False) posted_date = db.Column(db.Date, nullable=False) sort_rank = db.Column(db.Integer, nullable=False, default=0) + is_trending = db.Column(db.Boolean, nullable=False, default=False) summary = db.Column(db.Text, nullable=False, default="") description = db.Column(db.Text, nullable=False, default="") additional_description_json = db.Column(db.Text, nullable=True) @@ -333,7 +337,7 @@ def resolve_location(raw: str) -> dict | None: * ``{"kind": "state", "state": "PR", "label": "Puerto Rico", "store": <anchor>}`` when the text names a whole state or territory — the result set is then every role in that state, with no radius applied. - * ``{"kind": "store", "store": <Store>, "label": "Cleveland, OH"}`` when the + * ``{"kind": "store", "store": <Store>, "label": "Rochester, NY"}`` when the text names a city, a "City, ST" pair or a ZIP — the radius then applies. """ text = (raw or "").strip() @@ -643,8 +647,7 @@ def pin_card_svg(store: Store, width: int = 490, height: int = 230) -> str: def trending_jobs() -> list[Job]: - jobs = [db.session.get(Job, jid) for jid in content.TRENDING_JOB_IDS] - return [j for j in jobs if j is not None] + return Job.query.filter_by(is_trending=True).order_by(Job.job_id).all() def related_jobs(job: Job, limit: int = 3) -> list[Job]: diff --git a/sites/walmart_careers/catalog_source.py b/sites/walmart_careers/catalog_source.py index dbb9e0bc..85ae8e7c 100644 --- a/sites/walmart_careers/catalog_source.py +++ b/sites/walmart_careers/catalog_source.py @@ -589,7 +589,7 @@ ("2110", "Full time", "WD,WE", 15.50, 26.00, 3, None), ("2050", "Part time", "WE,SE", 17.50, 28.00, 2, None), ("471", "Full time", "WD,SD", 15.00, 25.00, 4, None), - ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, None), + ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, {"job_id": "CP-1236-10888"}), ("5388", "Full time", "WN", 16.50, 27.00, 2, None), ], }, @@ -1149,6 +1149,9 @@ ("2073", "Intern", "WD,FX", 16.50, 20.50, 1, None), ("4137", "Intern", "WE,FX", 17.00, 21.00, 1, None), ("2503", "Intern", "WD,SD", 15.00, 19.00, 1, None), + ("2050", "Intern", "WD,FX", 16.25, 20.25, 1, None), + ("1179", "Intern", "WE,FX", 16.75, 20.75, 1, None), + ("3387", "Intern", "WD,SD", 15.50, 19.50, 1, None), ], }, { @@ -1172,6 +1175,8 @@ "placements": [ ("6608", "Intern", "WD,FX", 18.00, 22.00, 1, None), ("6318", "Intern", "WE,FX", 16.50, 20.50, 1, None), + ("8259", "Intern", "WD,SD", 17.25, 21.25, 1, None), + ("6216", "Intern", "WE,FX", 17.75, 21.75, 1, None), ], }, ] @@ -1220,6 +1225,8 @@ {"job_id": "R-2463275"}), ("10101", "Full time", 132000, 264000, "Regular/Permanent", ("computer science, computer engineering, or related area", 5, 8, 3), None), + ("12200", "Full time", 128000, 246000, "Regular/Permanent", + ("computer science, electrical engineering, or related area", 6, 9, 3), None), ], }, { @@ -1249,6 +1256,8 @@ ("computer engineering, software engineering, or related area", 4, 7, 2), None), ("10101", "Full time", 96000, 192000, "Regular/Permanent", ("information systems, computer science, or related area", 2, 4, 1), None), + ("12200", "Full time", 105000, 195000, "Regular/Permanent", + ("embedded systems, computer engineering, or related area", 3, 6, 2), None), ], }, { @@ -1272,7 +1281,7 @@ "software at scale.", "placements": [ ("12200", "Full time", 90000, 180000, "Regular/Permanent", - ("computer science or related area", 2, 4, 3), None), + ("computer science or related area", 2, 4, 3), {"job_id": "R-2413636"}), ], }, { @@ -1295,11 +1304,13 @@ "product teams.", "placements": [ ("10101", "Full time", 110000, 220000, "Regular/Permanent", - ("business, analytics, engineering, or related area", 5, 7, 2), None), + ("business, analytics, engineering, or related area", 5, 7, 2), {"job_id": "R-2414279"}), ("11807", "Full time", 132000, 264000, "Regular/Permanent", ("computer science, business, or related area", 6, 9, 3), None), ("11003", "Full time", 90000, 180000, "Regular/Permanent", ("marketing, business, or related area", 4, 6, 1), None), + ("12200", "Full time", 118000, 225000, "Regular/Permanent", + ("electrical engineering, product design, or related area", 5, 8, 3), None), ], }, { @@ -1325,6 +1336,8 @@ "placements": [ ("10101", "Full time", 130000, 260000, "Regular/Permanent", ("business, engineering, or related area", 8, 11, 4), None), + ("12200", "Full time", 125000, 245000, "Regular/Permanent", + ("electrical engineering, business, or related area", 7, 10, 4), None), ], }, { @@ -1354,6 +1367,8 @@ ("applied mathematics, statistics, or related area", 3, 5, 1), None), ("11500", "Full time", 100000, 175000, "Regular/Permanent", ("operations research, statistics, or related area", 3, 6, 2), None), + ("12200", "Full time", 112000, 205000, "Regular/Permanent", + ("data science, statistics, or related area", 4, 7, 2), None), ], }, { @@ -1379,6 +1394,8 @@ ("information technology, cybersecurity, or related area", 5, 8, 3), None), ("11807", "Full time", 140000, 280000, "Regular/Permanent", ("computer science, cybersecurity, or related area", 6, 9, 4), None), + ("12200", "Full time", 122000, 235000, "Regular/Permanent", + ("information assurance, computer science, or related area", 4, 7, 3), None), ], }, { @@ -1453,6 +1470,8 @@ "placements": [ ("11807", "Full time", 150000, 275000, "Regular/Permanent", ("psychology, human-computer interaction, or related area", 7, 10, 4), None), + ("12200", "Full time", 138000, 255000, "Regular/Permanent", + ("cognitive science, design research, or related area", 6, 9, 3), None), ], }, { @@ -1535,6 +1554,8 @@ ("accounting, finance, or related area", 5, 7, 3), None), ("11500", "Full time", 90000, 180000, "Regular/Permanent", ("finance, economics, or related area", 4, 6, 2), None), + ("12200", "Full time", 98000, 190000, "Regular/Permanent", + ("corporate finance, accounting, or related area", 3, 5, 2), None), ], }, { @@ -1562,6 +1583,8 @@ ("accounting, business, or related area", 2, 5, 1), None), ("11500", "Full time", 72000, 134000, "Regular/Permanent", ("economics, finance, or related area", 3, 5, 2), None), + ("12200", "Full time", 71000, 132000, "Regular/Permanent", + ("finance, business analytics, or related area", 1, 3, 1), None), ], }, { @@ -1613,6 +1636,8 @@ ("human resources or related area", 3, 5, 2), None), ("11109", "Full time", 78000, 146000, "Regular/Permanent", ("business administration or related area", 2, 4, 1), None), + ("12200", "Full time", 82000, 152000, "Regular/Permanent", + ("organizational psychology, human resources, or related area", 4, 6, 2), None), ], }, { @@ -1662,6 +1687,8 @@ ("marketing, advertising, or related area", 6, 8, 3), None), ("11500", "Full time", 105000, 200000, "Regular/Permanent", ("communications, marketing, or related area", 5, 7, 2), None), + ("12200", "Full time", 102000, 196000, "Regular/Permanent", + ("brand management, marketing, or related area", 4, 6, 2), None), ], }, { @@ -1684,6 +1711,8 @@ "placements": [ ("11109", "Full time", 65000, 120000, "Regular/Permanent", ("marketing or related area", 2, 4, 1), None), + ("12200", "Full time", 68000, 126000, "Regular/Permanent", + ("marketing, media studies, or related area", 3, 5, 2), None), ], }, { @@ -1808,6 +1837,8 @@ ("supply chain management or related area", 3, 5, 2), None), ("10101", "Full time", 86000, 166000, "Regular/Permanent", ("industrial engineering, logistics, or related area", 4, 6, 2), None), + ("12200", "Full time", 88000, 168000, "Regular/Permanent", + ("operations management, logistics, or related area", 2, 4, 1), None), ], }, { @@ -1830,6 +1861,8 @@ "placements": [ ("11500", "Full time", 66000, 122000, "Regular/Permanent", ("business analytics, economics, or related area", 2, 4, 1), None), + ("12200", "Full time", 70000, 128000, "Regular/Permanent", + ("operations analytics, business, or related area", 3, 5, 2), None), ], }, { @@ -1857,6 +1890,8 @@ ("business, marketing, or supply chain", 2, 1, 2), None), ("10101", "Intern", 66000, 92000, "Intern (Fixed Term)", ("business administration or merchandising", 2, 1, 1), None), + ("11500", "Intern", 62000, 88000, "Intern (Fixed Term)", + ("business, merchandising, or analytics", 1, 2, 1), None), ], }, { @@ -1882,6 +1917,8 @@ ("computer science or computer engineering", 2, 1, 1), None), ("10101", "Intern", 72000, 98000, "Intern (Fixed Term)", ("computer science or information systems", 1, 2, 1), None), + ("11003", "Intern", 76000, 102000, "Intern (Fixed Term)", + ("software engineering or computer engineering", 3, 2, 1), None), ], }, { @@ -1905,6 +1942,8 @@ "placements": [ ("10101", "Intern", 60000, 84000, "Intern (Fixed Term)", ("finance, accounting, or economics", 2, 1, 1), None), + ("11500", "Intern", 58000, 82000, "Intern (Fixed Term)", + ("accounting or business administration", 1, 2, 1), None), ], }, { @@ -1927,6 +1966,54 @@ "placements": [ ("11003", "Intern", 70000, 96000, "Intern (Fixed Term)", ("statistics, data science, or economics", 2, 1, 1), None), + ("11807", "Intern", 74000, 100000, "Intern (Fixed Term)", + ("computer science, statistics, or mathematics", 1, 3, 1), None), ], }, ] + + +# --------------------------------------------------------------------------- # +# Hub copy for the four is_hub offices, keyed by store number: +# (display name, blurb, image file). Seeded onto Store.hub_name / hub_blurb / +# hub_image so the locations and career-area templates read it from the DB. +# --------------------------------------------------------------------------- # +HUB_COPY = { + "10101": ( + "Northwest Arkansas", + "Northwest Arkansas offers trails, local eats, and the Crystal Bridges Museum - while our " + "12 new Home Office buildings reflect the company's story through thoughtful design.", + "loc-nwa.jpg", + ), + "11807": ( + "Sunnyvale", + "A weekend hike through the mountains. An evening walk next to the ocean. A quick visit to a " + "museum. The best of both worlds - work and leisure - are waiting for you right here.", + "loc-sunnyvale.jpg", + ), + "11003": ( + "Hoboken", + "Just across from Lower Manhattan, Hoboken is a walkable, character-filled town on the Hudson " + "with a truly unique charm.", + "loc-hoboken.jpg", + ), + "11500": ( + "Dallas", + "Our Dallas office anchors merchandising, finance and supply chain teams in the middle of one " + "of the fastest-growing metros in the country.", + "loc-dc-metro.jpg", + ), +} + + +# --------------------------------------------------------------------------- # +# Job ids surfaced as "Trending roles" on the home page, the hiring page and the +# logged-out saved-roles page. Seeded onto Job.is_trending, which is what the +# handlers query. These three placements pin their job_id in `extras` so a +# catalog edit cannot silently point this list at a different posting. +# --------------------------------------------------------------------------- # +TRENDING_JOB_IDS = [ + "R-2414279", + "R-2413636", + "CP-1236-10888", +] diff --git a/sites/walmart_careers/seed_data.py b/sites/walmart_careers/seed_data.py index 8655fb10..73b9c5a3 100644 --- a/sites/walmart_careers/seed_data.py +++ b/sites/walmart_careers/seed_data.py @@ -7,17 +7,16 @@ """ from __future__ import annotations +import importlib.util import json import os import random -import re import shutil from datetime import date, datetime, timedelta from pathlib import Path os.environ.setdefault("WEBSYN_SKIP_BOOTSTRAP", "1") -import _content as content_module import catalog_source as source from _content import MIRROR_REFERENCE_DATE from app import ( @@ -142,6 +141,7 @@ def _build_stores() -> dict[str, Store]: for row in source.STORES: (number, banner, location_name, street, city, state, zip_code, lat, lng, is_hub, is_office, _brand) = row + hub_name, hub_blurb, hub_image = source.HUB_COPY.get(number, (None, None, None)) store = Store( store_number=number, banner=banner, @@ -154,6 +154,9 @@ def _build_stores() -> dict[str, Store]: lng=lng, is_hub=is_hub, is_office=is_office, + hub_name=hub_name, + hub_blurb=hub_blurb, + hub_image=hub_image, ) db.session.add(store) stores[number] = store @@ -179,6 +182,7 @@ def _hero_for(population: str, brand: str, index: int) -> list[str]: def _build_jobs(areas, categories, stores) -> list[Job]: brands = _store_brand() + trending = set(source.TRENDING_JOB_IDS) used_ids: set[str] = set() for family in source.HOURLY_FAMILIES: for placement in family["placements"]: @@ -239,6 +243,7 @@ def _build_jobs(areas, categories, stores) -> list[Job]: max_pay=max_pay, posted_date=MIRROR_REFERENCE_DATE - timedelta(days=RNG.randint(1, 120)), sort_rank=0, + is_trending=job_id in trending, summary=family["summary"].format(**fmt), description="\n\n".join(paragraphs), additional_description_json=dumps_json( @@ -303,6 +308,7 @@ def _build_jobs(areas, categories, stores) -> list[Job]: max_pay=max_pay, posted_date=MIRROR_REFERENCE_DATE - timedelta(days=RNG.randint(1, 120)), sort_rank=0, + is_trending=job_id in trending, summary=family["summary"].format(**fmt), description="\n\n".join(paragraphs), additional_description_json=None, @@ -405,338 +411,22 @@ def _find_job(title: str, store_number: str) -> Job: # --------------------------------------------------------------------------- # -# Build-time invariants. Only ever called from build_seed_database(). +# Build-time invariant checks. +# +# The benchmark task locators and their expected answers live in +# scripts_dev/assert_distractors.py, which is git-ignored and docker-ignored and +# therefore absent from the shipped tree. The freezer loads it by path when it is +# there, so a developer rebuild still fails on a catalog edit that breaks a task; +# a tree without it builds the same database and skips the checks. # --------------------------------------------------------------------------- # -# (task index, title, locator kwargs, expected job_id). The locator is what the -# task text tells an agent to look for; the check below proves it resolves to -# exactly one posting in the catalog. -TASK_TARGETS = [ - (0, "Optician", {"city": "Wichita"}, "CP-5991-11240"), - (1, "Staff, Software Engineer - Backend / ML", {"city": "Sunnyvale"}, "R-2463275"), - (2, "Freight Handler", {"store": "9054"}, "CP-9054-10921"), - (3, "Pharmacy Technician", {"city": "Bentonville"}, "CP-5260-11137"), - (4, "Merchandising and Stocking Associate", {"state": "TX"}, "CP-4750-11184"), - (5, "Senior Software Engineer", {"city": "Hoboken"}, "R-2411668"), - (6, "Online Order Filling Team Supervisor", {"city": "Cleveland"}, "CP-2073-10625"), - (7, "Merchandising Intern", {"store": "11109"}, "R-2442353"), - (8, "Auto Care Center Technician", {"city": "Brookhaven"}, "CP-1230-11711"), - (8, "Auto Care Center Technician", {"city": "Hazlehurst"}, "CP-954-11141"), - (9, "Freight Handler", {"store": "6038"}, "CP-6038-10642"), - (9, "Freight Handler", {"store": "9046"}, "CP-9046-10913"), - (10, "Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery)", - {"city": "Bentonville"}, "R-2451180"), - (10, "Senior Manager, Delivery Search, Arrival & Matching (Last Mile Delivery)", - {"city": "Hoboken"}, "R-2439632"), - (11, "Yard Driver-Off Property", {"city": "Williamsburg"}, "CP-6088-10488"), - (12, "Asset Protection Associate", {"store": "5991"}, "CP-5991-10486"), - (13, "Pharmacy Technician", {"city": "Tacoma"}, "CP-4137-11560"), - (14, "eCom Warehouse Worker", {"store": "9046"}, "CP-9046-11101"), - (16, "Cashier & Front End Services", {"city": "Bayamon"}, "CP-2503-10726"), - (17, "Cosmetics Cashier", {"city": "Bayamon"}, "CP-2503-11683"), - (18, "Class A CDL Truck Driver", {"city": "Ottawa"}, "CP-6014-12091"), - (18, "Class A CDL Truck Driver", {"city": "Williamsburg"}, "CP-6088-11446"), - (19, "Online Order Filling Team Supervisor", {"city": "Topeka"}, "CP-1179-11268"), -] - - -def _assert_distractors() -> None: - """Build-time invariants behind the 20 benchmark tasks. - - Only ever called from build_seed_database(); never at import, bootstrap or - /reset time. Every task in tasks.jsonl has a block here: the locator in the - task text must resolve to exactly one posting, the natural query must return - enough near-misses, and whatever the task asks the agent to report must have - a unique answer. - """ - from app import search_jobs - - problems: list[str] = [] - - def base(**kwargs) -> dict: - filters = { - "q": "", "area": [], "category": [], "brand": [], "shift": [], - "type": [], "rate": [], "loc": "", "radius": 25, - "sort": "relevance", "page": 1, "tab": "jobs", - } - filters.update(kwargs) - return filters - - def results(**kwargs) -> list[Job]: - jobs, _location, _failed = search_jobs(base(**kwargs)) - return jobs - - def locate(title: str, *, city: str = "", state: str = "", store: str = "") -> list[Job]: - rows = Job.query.filter_by(title=title).all() - if city: - rows = [j for j in rows if j.store.city == city] - if state: - rows = [j for j in rows if j.store.state == state] - if store: - rows = [j for j in rows if j.store.store_number == store] - return sorted(rows, key=lambda j: j.job_id) - - def only(label: str, rows: list[Job], job_id: str) -> Job | None: - if len(rows) != 1: - problems.append(f"{label}: {len(rows)} postings match the task locator (want 1)") - return None - if rows[0].job_id != job_id: - problems.append(f"{label}: resolved to {rows[0].job_id}, expected {job_id}") - return rows[0] - - def breadth(label: str, rows: list[Job], full_matches: list[Job], minimum: int = 6) -> None: - """Catalog breadth rule: >= `minimum` results, <= 50% of them full matches.""" - if len(rows) < minimum: - problems.append(f"{label}: only {len(rows)} results (want >= {minimum})") - elif len(full_matches) > len(rows) / 2: - problems.append( - f"{label}: {len(full_matches)}/{len(rows)} results satisfy every constraint (want <= 50%)" - ) - - # --- structural volumes ------------------------------------------------ - if Job.query.count() != 200: - problems.append(f"expected 200 jobs, found {Job.query.count()}") - for category in Category.query.all(): - count = Job.query.filter_by(category_id=category.id).count() - if count < 4: - problems.append(f"category {category.name!r} has only {count} jobs") - for store in Store.query.all(): - count = Job.query.filter_by(store_id=store.id).count() - if count < 3: - problems.append(f"store #{store.store_number} has only {count} jobs") - states = sorted({s.state for s in Store.query.all()}) - for state in states: - count = Job.query.join(Store).filter(Store.state == state).count() - if count < 8: - problems.append(f"state {state} has only {count} jobs") - shift_counts = {name: 0 for name in source.SHIFT_CODES.values()} - for job in Job.query.all(): - for name in job.shifts: - shift_counts[name] += 1 - for name, count in sorted(shift_counts.items()): - if count < 24: - problems.append(f"shift {name!r} appears on only {count} jobs") - - # --- global de-leak invariants ---------------------------------------- - hashtags = [f.get("hashtag") for f in source.HOURLY_FAMILIES] - if len(set(hashtags)) != len(hashtags): - problems.append("hourly hashtags are not unique per title family") - quals = [j.min_qualifications_json for j in Job.query.filter_by(population="salaried").all()] - if len(set(quals)) != len(quals): - problems.append("salaried minimum-qualification texts are not unique per posting") - target_ids = {job_id for _idx, _title, _loc, job_id in TASK_TARGETS} - for job_id in content_module.TRENDING_JOB_IDS: - if job_id in target_ids: - problems.append(f"trending role {job_id} is a benchmark task target") - if db.session.get(Job, job_id) is None: - problems.append(f"trending role {job_id} does not exist") - - # --- every task locator resolves to exactly one posting ---------------- - resolved: dict[tuple[int, str], Job] = {} - for index, title, locator, job_id in TASK_TARGETS: - label = f"task {index} ({title} {locator})" - job = only(label, locate(title, **locator), job_id) - if job is not None: - resolved[(index, job_id)] = job - - def target(index: int, job_id: str) -> Job | None: - return resolved.get((index, job_id)) - - # --- task 0: Optician in Wichita, KS ---------------------------------- - opticians = results(q="optician") - breadth("task 0 (q=optician)", opticians, - [j for j in opticians if j.title == "Optician" - and j.store.banner == "Neighborhood Market" and j.store.city == "Wichita"]) - - # --- task 1: Staff SWE in Sunnyvale — Option 2 unique among its family -- - staff = Job.query.filter_by(title="Staff, Software Engineer - Backend / ML").all() - if len({j.min_qualifications[1] for j in staff}) != len(staff): - problems.append("task 1: the Staff SWE postings share an Option 2 text") - - # --- task 2: Freight Handler #9054 — window + positions --------------- - freight = results(q="freight handler") - breadth("task 2 (q=freight handler)", freight, - [j for j in freight if j.title == "Freight Handler" and j.store.store_number == "9054"]) - - # --- task 3: Pharmacy Technician in Bentonville ----------------------- - pharmacy = [j for j in Job.query.all() if j.category and j.category.slug == "pharmacy-services"] - breadth("task 3 (Pharmacy Services category)", pharmacy, - [j for j in pharmacy if j.title == "Pharmacy Technician" and j.store.city == "Bentonville"]) - bentonville_pt = target(3, "CP-5260-11137") - if bentonville_pt is not None: - siblings = [j for j in Job.query.filter_by(title="Pharmacy Technician").all() - if j.job_id != bentonville_pt.job_id] - if any(j.hashtag != bentonville_pt.hashtag for j in siblings): - problems.append("task 3: Pharmacy Technician hashtags differ inside one title family") - if all(j.positions_available == bentonville_pt.positions_available for j in siblings): - problems.append("task 3: every Pharmacy Technician posting lists the same open positions") - - # --- task 4: Sam's Club / Part time / Weekend Overnight / <= $20 in TX -- - sams_pt_overnight = results(brand=["Sam's Club"], type=["Part time"], shift=["Weekend Overnight"]) - cheap = [j for j in sams_pt_overnight if float(j.max_pay) <= 20.00] - cheap_tx = [j for j in cheap if j.store.state == "TX"] - breadth("task 4 (Sam's Club PT weekend overnight)", sams_pt_overnight, cheap_tx) - if len(cheap_tx) != 1: - problems.append(f"task 4: {len(cheap_tx)} matching TX postings at or under $20/hr (want 1)") - elif cheap_tx[0].job_id != "CP-4750-11184": - problems.append(f"task 4: resolved to {cheap_tx[0].job_id}") - - # --- task 5: Full time Technology in Hoboken topping $200,000 --------- - hoboken_tech = results(area=["technology"], type=["Full time"], loc="Hoboken, NJ", radius=25) - over_200k = [j for j in hoboken_tech if float(j.max_pay) > 200000] - breadth("task 5 (Full time Technology near Hoboken)", hoboken_tech, over_200k) - if len(over_200k) != 1: - problems.append(f"task 5: {len(over_200k)} Hoboken tech roles top out above $200,000 (want 1)") - - # --- task 6: Cleveland, OH / Full time / Weekday Day ------------------ - cleveland = results(loc="Cleveland, OH", radius=25) - cleveland_full = results(loc="Cleveland, OH", radius=25, type=["Full time"], shift=["Weekday Day"]) - supervisors = [j for j in cleveland_full if j.title == "Online Order Filling Team Supervisor"] - breadth("task 6 (within 25 miles of Cleveland)", cleveland, supervisors) - if len(supervisors) != 1: - problems.append(f"task 6: {len(supervisors)} Online Order Filling Team Supervisor roles near Cleveland") - - # --- task 7: Students / Intern / Sam's Club --------------------------- - interns = results(area=["students"], type=["Intern"]) - sams_interns = [j for j in interns if j.brand == "Sam's Club"] - merch_interns = [j for j in sams_interns if "Merchandising" in j.title] - breadth("task 7 (Students interns)", interns, merch_interns) - if len(merch_interns) != 1: - problems.append(f"task 7: {len(merch_interns)} Sam's Club merchandising internships (want 1)") - elif merch_interns[0].store.street != "2101 SE Simple Savings Dr": - problems.append("task 7: the Sam's Club internship street address moved") - - # --- task 8: two MS Auto Care postings, different position counts ------ - ms_auto = [j for j in Job.query.join(Store).filter(Store.state == "MS").all() - if j.title == "Auto Care Center Technician"] - if len(ms_auto) != 2: - problems.append(f"task 8: {len(ms_auto)} Auto Care Center Technician postings in MS (want 2)") - elif ms_auto[0].positions_available == ms_auto[1].positions_available: - problems.append("task 8: the two MS Auto Care postings list the same open positions") - - # --- task 9: two Marcy Freight Handlers, different windows ------------ - marcy_freight = [j for j in Job.query.join(Store).filter(Store.city == "Marcy").all() - if j.title == "Freight Handler"] - if len(marcy_freight) != 2: - problems.append(f"task 9: {len(marcy_freight)} Freight Handler postings in Marcy, NY (want 2)") - elif marcy_freight[0].shift_time == marcy_freight[1].shift_time: - problems.append("task 9: the two Marcy Freight Handler postings share a shift start window") - - # --- task 10: two Last Mile postings, different Option 2 years -------- - last_mile = Job.query.filter(Job.title.like("Senior Manager, Delivery Search%")).all() - if len(last_mile) != 2: - problems.append(f"task 10: {len(last_mile)} Last Mile Delivery postings (want 2)") - else: - years = [_years_in(j.min_qualifications[1]) for j in last_mile] - if years[0] == years[1] or None in years: - problems.append("task 10: the two Last Mile postings do not differ in Option 2 years") - - # --- task 11: Yard Driver search set --------------------------------- - yard = results(q="yard driver") - breadth("task 11 (q=yard driver)", yard, - [j for j in yard if j.title == "Yard Driver-Off Property" - and j.store.city == "Williamsburg"]) - - # --- tasks 12 / 17: the seeded saved lists must disambiguate ---------- - for email, predicate, label in ( - ("bob.c@test.com", lambda j: j.store.banner == "Neighborhood Market", - "task 12 (bob's Neighborhood Market saved role)"), - ("alice.j@test.com", lambda j: j.employment_type == "Part time", - "task 17 (alice's Part time saved role)"), - ): - user = User.query.filter_by(email=email).one() - rows = [db.session.get(Job, s.job_id) for s in - SavedJob.query.filter_by(user_id=user.id).order_by(SavedJob.id).all()] - if len(rows) < 3: - problems.append(f"{label}: only {len(rows)} saved roles (want >= 3)") - matches = [j for j in rows if predicate(j)] - if len(matches) != 1: - problems.append(f"{label}: {len(matches)} saved roles match (want exactly 1)") - - # --- task 13: Pharmacy Technician in Tacoma -------------------------- - tacoma = results(q="pharmacy technician") - breadth("task 13 (q=pharmacy technician)", tacoma, - [j for j in tacoma if j.title == "Pharmacy Technician" and j.store.city == "Tacoma"]) - - # --- task 14: eCom Warehouse Worker at #9046 ------------------------- - ecom = results(q="ecom warehouse worker") - breadth("task 14 (q=ecom warehouse worker)", ecom, - [j for j in ecom if j.title == "eCom Warehouse Worker" - and j.store.store_number == "9046"]) - - # --- task 15: david's profile starts different from the target values -- - david = User.query.filter_by(email="david.k@test.com").one() - if david.phone == "479-555-0199" or (david.city, david.state) == ("Rogers", "AR"): - problems.append("task 15: david's seeded profile already holds the target values") - - # --- task 16: PR cashiers --------------------------------------------- - pr_cashiers = [j for j in Job.query.join(Store).filter(Store.state == "PR").all() - if "Cashier" in j.title and j.population == "hourly"] - with_weekday_day = [j for j in pr_cashiers if "Weekday Day" in j.shifts] - if len(pr_cashiers) < 5: - problems.append(f"task 16: only {len(pr_cashiers)} PR cashier postings") - if len(with_weekday_day) < 3: - problems.append("task 16: fewer than 3 PR cashier postings list Weekday Day") - if len(with_weekday_day) > len(pr_cashiers) / 2 and len(pr_cashiers) - len(with_weekday_day) < 1: - problems.append("task 16: every PR cashier posting lists Weekday Day — no near-miss") - tops = sorted((j.positions_available for j in with_weekday_day), reverse=True) - if len(tops) >= 2 and tops[0] == tops[1]: - problems.append("task 16: the PR Weekday Day cashiers tie on open positions") - without = [j for j in pr_cashiers if j not in with_weekday_day] - if tops and not any(j.positions_available >= tops[0] - 1 for j in without): - problems.append("task 16: no near-miss PR cashier with a comparable open-position count") - # both routes to the Puerto Rico result set must work - by_state = results(q="cashier", loc="Puerto Rico") - by_radius = results(q="cashier", loc="Bayamon, PR", radius=60) - for label, rows in (("loc=Puerto Rico", by_state), ("loc=Bayamon, PR r=60", by_radius)): - reachable = {j.job_id for j in rows} - missing = [j.job_id for j in pr_cashiers if j.job_id not in reachable] - if missing: - problems.append(f"task 16: {label} misses PR cashier postings {missing}") - - # --- task 18: Drivers category, Ottawa vs Williamsburg ---------------- - drivers = results(area=["supply-chain-and-transportation"], category=["drivers"]) - cdl = [j for j in drivers if j.title == "Class A CDL Truck Driver" - and j.store.city in ("Ottawa", "Williamsburg")] - breadth("task 18 (Drivers category)", drivers, cdl, minimum=6) - ottawa = target(18, "CP-6014-12091") - williamsburg = target(18, "CP-6088-11446") - if ottawa is not None and williamsburg is not None: - if ottawa.positions_available == williamsburg.positions_available: - problems.append("task 18: the two CDL postings list the same open positions") - if ottawa.shift_time == williamsburg.shift_time: - problems.append("task 18: the two CDL postings share a shift start window") - - # --- task 19: Digital Pickup and Delivery, Full time, fewest positions -- - pickup = results(area=["stores-and-clubs"], category=["digital-pickup-and-delivery"]) - pickup_full = [j for j in pickup if j.employment_type == "Full time"] - counts = sorted(j.positions_available for j in pickup_full) - breadth("task 19 (Digital Pickup and Delivery)", pickup, - [j for j in pickup_full if counts and j.positions_available == counts[0]]) - if len(counts) < 3: - problems.append(f"task 19: only {len(counts)} Full time digital pickup postings") - elif counts[0] == counts[1]: - problems.append("task 19: the fewest-open-positions posting is not unique") - - # --- relevance: no target is pinned to rank 1 of its natural query ----- - for index, query, job_id in ( - (0, "optician", "CP-5991-11240"), - (2, "freight handler", "CP-9054-10921"), - (13, "pharmacy technician", "CP-4137-11560"), - ): - rows = results(q=query) - ids = [j.job_id for j in rows] - if ids[:1] == [job_id]: - problems.append(f"task {index}: the target is the first result for {query!r}") - - if problems: - raise AssertionError( - "seed distractor checks failed:\n - " + "\n - ".join(problems) - ) - - -def _years_in(text: str) -> int | None: - match = re.search(r"(\d+)\s+years", text) - return int(match.group(1)) if match else None +def _load_distractor_checks(): + path = BASE_DIR / "scripts_dev" / "assert_distractors.py" + if not path.exists(): + return None + spec = importlib.util.spec_from_file_location("walmart_careers_assert_distractors", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module.assert_distractors def build_seed_database() -> None: @@ -744,13 +434,17 @@ def build_seed_database() -> None: DB_PATH.parent.mkdir(parents=True, exist_ok=True) if DB_PATH.exists(): DB_PATH.unlink() + checks = _load_distractor_checks() with app.app_context(): db.drop_all() db.create_all() seed_database(force=True) seed_benchmark_users(force=True) - _assert_distractors() + if checks is not None: + checks() shutil.copyfile(DB_PATH, INSTANCE_SEED_DIR / "walmart_careers.db") + if checks is None: + print("scripts_dev/assert_distractors.py not present - build-time invariants skipped.") if __name__ == "__main__": diff --git a/sites/walmart_careers/static/js/.gitkeep b/sites/walmart_careers/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/walmart_careers/templates/area.html b/sites/walmart_careers/templates/area.html index 7720bb8e..9b59eaf5 100644 --- a/sites/walmart_careers/templates/area.html +++ b/sites/walmart_careers/templates/area.html @@ -62,13 +62,12 @@ <h2>Explore our benefits</h2> <h2>Our hubs</h2> <div class="hub-grid"> {% for hub in hubs %} - {% set copy = content.HUB_COPY.get(hub.store_number) %} - {% if copy %} + {% if hub.hub_blurb %} <div class="hub-card"> - <img src="{{ url_for('static', filename='images/' ~ copy[2]) }}" alt="{{ copy[0] }}"> + <img src="{{ url_for('static', filename='images/' ~ hub.hub_image) }}" alt="{{ hub.hub_name }}"> <div class="cap"> - <h3>{{ copy[0] }}</h3> - <p>{{ copy[1] }}</p> + <h3>{{ hub.hub_name }}</h3> + <p>{{ hub.hub_blurb }}</p> <a class="btn btn-secondary btn-sm" href="{{ url_for('results', loc=hub.city ~ ', ' ~ hub.state, radius=25) }}">See roles near {{ hub.city }}</a> </div> diff --git a/sites/walmart_careers/templates/locations.html b/sites/walmart_careers/templates/locations.html index c6a8eaf7..cea5bc83 100644 --- a/sites/walmart_careers/templates/locations.html +++ b/sites/walmart_careers/templates/locations.html @@ -12,13 +12,12 @@ <h1 style="font-size:44px;font-weight:400;margin:0 0 12px">{{ content.LOCATIONS_ <h2>Hubs around the world</h2> <div class="hub-grid"> {% for hub in hubs %} - {% set copy = content.HUB_COPY.get(hub.store_number) %} <div class="hub-card"> - <img src="{{ url_for('static', filename='images/' ~ (copy[2] if copy else 'loc-international.jpg')) }}" - alt="{{ copy[0] if copy else hub.city }}"> + <img src="{{ url_for('static', filename='images/' ~ (hub.hub_image or 'loc-international.jpg')) }}" + alt="{{ hub.hub_name or hub.city }}"> <div class="cap"> - <h3>{{ copy[0] if copy else hub.city }}</h3> - <p>{{ copy[1] if copy else 'A Walmart hub location.' }}</p> + <h3>{{ hub.hub_name or hub.city }}</h3> + <p>{{ hub.hub_blurb or 'A Walmart hub location.' }}</p> <p class="form-note">{{ hub.location_name }} — {{ hub.street }}, {{ hub.city }}, {{ hub.state }} {{ hub.zip }} — {{ counts[hub.id] }} open roles</p> <a class="btn btn-secondary btn-sm" From 85a4c9f09c2a6e008e3ebc0cda0dddb14a9534b3 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 13:41:13 -0500 Subject: [PATCH 10/15] fix(walmart_careers): push task targets off rank 1, widen distractor sets, tighten search, fix visual fidelity per audit Catalog and tasks - 7 new stores (Store 44 -> 51), three of them within 25 miles of Cleveland OH, plus 23 placements so every non-stateful task's natural result set has >= 6 rows with <= 50% full matches; alice's seeded saved list grows to 6 roles - every non-stateful task's answer now sits off rank 1 of its natural result set (build-time invariant, extended from 3 tasks to all 16) - search: stem tier needs a 6-char shared prefix with lengths within 3, and the substring tier became a word-prefix tier, so "technician" no longer matches "Technology" and "care" no longer lights up Healthcare - task 4 reworded to "whose posted pay range tops out at $20.00/hr or less"; task 15 continues to My applications and reports the existing confirmation - /careers-areas/students 302s to /results?area=students instead of 404 - Dallas hub image: loc-dallas.jpg (upstream carries no Dallas photo; the Global Tech office interior from the same CMS), loc-dc-metro.jpg deleted Visual fidelity (templates + CSS) - home: left-aligned hero and pill, no header pill on "/", button-less trending cards, career-area ribbon, values bento, benefits grid, milestone badges, associates strip and the white closing search - career-area pages: floating card over the undarkened photo, photo-left "Join our team" with a plain category list, benefits tiles, bento, quote band, hub links / associates strip, testimonial cards; Military uses the banner - results: one "Select +" pill per card with the whole card clickable, no filter count badge, globe glyph before EN in the inset account popover - locations: hero photo card and 3-across hub tiles with bare arrow links - login/register render through a stripped base; saved roles leads with the "Get more out of Walmart Careers" promo block - 12 unreferenced images and their dead constants removed Seed re-frozen with PYTHONHASHSEED=0: e8b680d00078eb591fd5b14f5918eeab (twice). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHd2JXJyHCSEKjBjSH8G47 --- sites/walmart_careers/CLAUDE.md | 7 +- sites/walmart_careers/_content.py | 168 ++++++-- sites/walmart_careers/app.py | 24 +- sites/walmart_careers/catalog_source.py | 34 +- sites/walmart_careers/seed_data.py | 5 +- sites/walmart_careers/static/css/site.css | 378 ++++++++++++++---- sites/walmart_careers/tasks.jsonl | 4 +- .../walmart_careers/templates/_job_card.html | 22 +- sites/walmart_careers/templates/area.html | 183 +++++++-- sites/walmart_careers/templates/base.html | 16 +- .../walmart_careers/templates/base_auth.html | 35 ++ sites/walmart_careers/templates/index.html | 178 ++++++--- .../walmart_careers/templates/locations.html | 54 ++- sites/walmart_careers/templates/login.html | 43 +- sites/walmart_careers/templates/register.html | 66 ++- sites/walmart_careers/templates/results.html | 1 - .../templates/saved_roles.html | 51 ++- 17 files changed, 953 insertions(+), 316 deletions(-) create mode 100644 sites/walmart_careers/templates/base_auth.html diff --git a/sites/walmart_careers/CLAUDE.md b/sites/walmart_careers/CLAUDE.md index 5a51bbfd..e62e366d 100644 --- a/sites/walmart_careers/CLAUDE.md +++ b/sites/walmart_careers/CLAUDE.md @@ -8,10 +8,10 @@ alt-port test container maps it to **41019**. | file | role | |---|---| | `app.py` | models, routes, scored search, deterministic SVG maps, bootstrap | -| `catalog_source.py` | the source catalog: areas, categories, 44 stores, 37 hourly + 29 salaried title families with explicit placements, hub copy, trending job ids | +| `catalog_source.py` | the source catalog: areas, categories, 51 stores, 37 hourly + 29 salaried title families with explicit placements, hub copy, trending job ids | | `seed_data.py` | turns the catalog into SQLite; `build_seed_database()` is the freezer | | `_content.py` | static chrome strings only: headings, boilerplate prose, design constants, US/PR map outlines | -| `templates/` | 19 Jinja2 templates + `_job_card.html` macro | +| `templates/` | 20 Jinja2 templates (`base.html`, the stripped `base_auth.html` for sign-in/register) + `_job_card.html` macro | | `static/` | `css/`, `js/`, `icons/`, `fonts/` in git; `images/` HF-managed | | `scripts_dev/` | local-only helpers and build-time invariants; gitignored and dockerignored | @@ -68,7 +68,8 @@ Brand chrome (`static/icons/`, `static/fonts/`) is committed; photography `cms.careers.walmart.com/content/dam/careers/...`, `careers.walmart.com/assets/svgs/...`, and the `EverydaySansUI` / `LivingDesign` font files from `i5.walmartimages.com`. `scripts_dev/harvest_assets.py` records the exact URL → filename mapping and re-downloads -them; images are then downscaled to 1600px wide (16 MB total). +them; images are then downscaled to 1600px wide. Upstream carries no Dallas hub photo, so +`loc-dallas.jpg` is the Global Tech office interior from the same CMS (see `harvest_assets.py`). ## Things that are deliberately not mirrored diff --git a/sites/walmart_careers/_content.py b/sites/walmart_careers/_content.py index db7a8fb5..14dadffd 100644 --- a/sites/walmart_careers/_content.py +++ b/sites/walmart_careers/_content.py @@ -19,14 +19,6 @@ HERO_HEADLINE_2 = "Next move, yours." SEARCH_PLACEHOLDER = "Search by team, department, keyword" -CAROUSEL = [ - ("Jorden", "Associate Merchant", "Corporate Careers", "corporate", "home-tile-corporate.png"), - ("Tatiana", "Software Engineer", "Tech Careers", "technology", "home-tile-tech.png"), - ("Jamaily", "Club Manager", "Stores & Clubs Careers", "stores-and-clubs", "home-tile-stores.png"), - ("Caleb", "Maintenance Tech", "Supply Chain Careers", "supply-chain-and-transportation", "home-tile-supply.png"), - ("Yasinya", "Pharmacy Tech", "Healthcare Careers", "healthcare", "home-tile-health.png"), -] - VALUES = [ ("Respect for the individual", "We listen, we support, and we help each other grow."), ("Service to the customer", "Everything starts with the people who shop with us."), @@ -42,24 +34,6 @@ ("Career growth opportunities", "Training, leadership programs, and clear paths to advance.", "benefit-growth.svg"), ] -BENEFIT_FOOTNOTE = ( - "That's just the beginning. We offer more perks specific to your work location and role." -) - -STAT_CARDS = [ - ("$1 billion", "invested in associate career training and development"), - ("75%", "of salaried managers began as hourly associates"), - ("300,000", "associates have earned a 10+ year badge"), - ("120,000", "U.S. associates have participated in Live Better U"), -] - -DAY_IN_THE_LIFE = [ - ("Store Coach", "Day in the life", "life-associates.jpg"), - ("Optician", "Day in the life", "life-8th-plate.jpg"), - ("Store Manager", "Day in the life", "life-crystal-bridges.jpg"), - ("Pharmacy Tech", "Day in the life", "life-amp.jpg"), -] - # --------------------------------------------------------------------------- # # Benefit tiles on the job detail page. Keyed by brand; hourly and salaried # postings surface a slightly different Live Better U line, exactly as upstream. @@ -389,3 +363,145 @@ def benefit_tiles_for(brand: str, population: str) -> list[tuple[str, str, str, "WY": "Wyoming", } STATE_CODES_BY_NAME = {name.lower(): code for code, name in sorted(STATE_NAMES.items())} + + +# --------------------------------------------------------------------------- # +# Home page chrome below the fold: the values bento, the milestone badges and +# the "See our associates in action" strip. Static marketing copy only. +# --------------------------------------------------------------------------- # +HOME_INTRO_HEADLINE = ("Grow your future.", "Make an impact.") +HOME_INTRO_CTA = "See our values in action" +BENEFITS_ASIDE = "That's just the beginning. We offer more perks specific to your work location and role." +BENEFITS_CTA = "Learn more about benefits" +MILESTONE_HEADING = "Here, every job is a step toward something greater" +# (figure sentence, badge label, style) — style picks the badge colour scheme. +MILESTONE_BADGES = [ + ("$1 billion invested in associate career training and development", "", "sky"), + ("75% of salaried managers began as hourly associates", "5 YEARS", "spark"), + ("300,000 associates have earned a 10+ year badge", "10 YEARS", "navy"), + ("120,000 U.S. associates have participated in Live Better U", "20 YEARS", "blue"), +] +ASSOCIATES_HEADING = "See our associates in action" +ASSOCIATES_BLURB = ( + "Every day, Walmart associates step up - solving problems, serving communities, and making a " + "difference. They don't just do the job; they bring it to life." +) +FIND_ROLE_HEADING = "Find the role that's a perfect fit" +FIND_ROLE_PLACEHOLDER = "Search by team, department, or keyword" + +# --------------------------------------------------------------------------- # +# Career-area page chrome (per area slug): the lower sections of the L1 pages. +# --------------------------------------------------------------------------- # +AREA_PAGE = { + "stores-and-clubs": { + "tiles": ("Purpose", "Growth", "Pride"), + "headline": "You power the experience for millions", + "cta": "See all stores and clubs roles", + "photos": ("area-stores-3.jpg", "area-stores-2.jpg"), + "quote": "At Walmart and Sam's Club, our stores and clubs are powered by people, dedicated " + "associates working together to create exceptional experiences for the communities " + "we serve.", + "testimonials": [ + ("Curtis", "Store Manager", "Every shift is a chance to make someone's day a little easier."), + ("D'Rogelio", "Store Manager", "You can be you in this environment and still succeed."), + ("Jamaily", "Club Manager", "I started on the floor. Now I run the building."), + ], + }, + "supply-chain-and-transportation": { + "tiles": ("Safety", "Scale", "Momentum"), + "headline": "Move what matters, at scale", + "cta": "See all supply chain roles", + "photos": ("area-supply-chain-2.jpg", "supply-drone.jpg"), + "quote": "Our supply chain associates move millions of items a day through a network that " + "reaches nearly every community in the country - and they do it safely.", + "testimonials": [ + ("Caleb", "Maintenance Tech", "The equipment is the most advanced I've worked on anywhere."), + ("Renee", "Yard Driver", "I know exactly how my work gets product to a shelf."), + ("Marcus", "Area Manager", "We promote from the floor. That's not a slogan here."), + ], + }, + "healthcare": { + "tiles": ("Care", "Community", "Growth"), + "headline": "Care for the communities you call home", + "cta": "See all healthcare roles", + "photos": ("area-healthcare.jpg", "jobhero-wm-2.jpg"), + "quote": "Our pharmacies, vision centers and clinics put affordable care within a short drive " + "of most of the country - and our associates make it personal.", + "testimonials": [ + ("Yasinya", "Pharmacy Tech", "Patients know my name. That's the part I love."), + ("Andre", "Optician", "Every fitting is a small problem to solve well."), + ("Priya", "Pharmacy Manager", "Walmart paid for my certification through Live Better U."), + ], + }, + "technology": { + "tiles": ("Belonging", "Impact"), + "headline": "Tech with real-world impact", + "cta": "See all technology roles", + "photos": ("area-technology-2.jpg", "supply-drone.jpg"), + "quote": "Our vision is strong here. Walmart Global Tech works at the forefront of " + "cutting-edge technologies inspired by the vision of transforming retail tech.", + "hubs_heading": "Three hubs. One mission. Endless possibilities", + "hubs_blurb": "Our hubs spark collaboration and innovation, so you're free to energize and push " + "boundaries from the space that serves you best.", + "testimonials": [ + ("Tatiana", "Software Engineer (iOS)", "The scale of what ships every week still amazes me."), + ("Christopher", "Senior Manager, Food Media Insights", + "We're data geeks, and the depth we get to explore here keeps us excited every single day."), + ("Antony", "Yield Manager", "I get to work on problems no other retailer has."), + ], + }, + "corporate": { + "tiles": ("Curiosity", "Ownership", "Impact"), + "headline": "Shape how the world shops", + "cta": "See all corporate roles", + "photos": ("area-corporate-2.jpg", "jobhero-corp-3.jpg"), + "quote": "From merchandising to finance to people, our home office teams make decisions that " + "reach 240 million customers a week.", + "hubs_heading": "Hubs built for the way you work", + "hubs_blurb": "Bentonville, Sunnyvale, Hoboken and Dallas: pick the space that serves you best.", + "testimonials": [ + ("Jorden", "Associate Merchant", "I own a category. At 26. That doesn't happen elsewhere."), + ("Nina", "Finance Manager", "The numbers are big, but the teams are small and close."), + ("Sam", "People Partner", "We hire for potential and then we invest in it."), + ], + }, + "Military": { + "tiles": ("Transition", "Translate", "Thrive"), + "headline": "Walmart supports Veterans", + "cta": "See all opportunities", + "photos": ("area-military.jpg", "military-banner.png"), + "quote": "Every day, thousands of veterans build careers at Walmart. Learn more about our " + "commitment to veterans and military families.", + "testimonials": [ + ("Mark", "Veteran, Store Coach", "My leadership experience translated on day one."), + ("Kim", "Store Manager", "Walmart's not only committed to the veteran - veteran spouses have just the same opportunity."), + ("Jeremy", "Club Manager", "SkillBridge got me in the door. The team kept me here."), + ], + }, +} +AREA_PAGE_DEFAULT = { + "tiles": ("Purpose", "Growth", "Pride"), + "headline": "Grow your future. Make an impact.", + "cta": "See all open roles", + "photos": ("area-stores-3.jpg", "area-stores-2.jpg"), + "quote": LIFE_AT_WALMART_QUOTE, + "testimonials": [], +} +INSPIRATION_HEADING = "Inspiration in every role" + +# Locations page hero and the promo block on the saved-roles page. +LOCATIONS_HERO_IMAGE = "loc-silicon-valley.jpg" +SAVED_PROMO_HEADLINE = ("Get more out of", "Walmart Careers") +SAVED_PROMO_BLURB = ( + "With an account you get role recommendations, create job alerts, and view your application " + "status from a personalized dashboard." +) +SAVED_PROMO_IMAGE = "area-healthcare.jpg" +SAVED_EMPTY_NOTE = "You have no saved roles." + +# Footer links on the stripped sign-in / register layout. +AUTH_FOOTER_LINKS = [ + "Give feedback", "Terms of Use", "Privacy Notice", "California Supply Chain Act", + "Your Privacy Choices", "Customer Privacy Center", "Notice at Collection", +] +AUTH_COPYRIGHT = "© 2026 Walmart. All Rights Reserved." diff --git a/sites/walmart_careers/app.py b/sites/walmart_careers/app.py index 2d3572bf..d56b7a37 100644 --- a/sites/walmart_careers/app.py +++ b/sites/walmart_careers/app.py @@ -443,21 +443,24 @@ def filters_query(filters: dict, **overrides) -> str: def _stem_match(token: str, blob_tokens: set[str]) -> bool: - """Loose match so 'optician' also surfaces 'Optical Services' rows. + """Loose match for inflected forms ('drivers' ~ 'driver', 'stocking' ~ 'stocker'). - Two words match when they share a prefix of at least five characters; the - search is scored, never a strict AND, so this only widens the result set. + Two words match when they share a prefix of at least six characters and + their lengths are within three of each other. A five-letter prefix was + too loose: 'technician' matched 'Technology' and pulled half the catalog + into a title search. The search is scored, never a strict AND, so this + tier only widens a result set. """ - if len(token) < 5: + if len(token) < 6: return False for other in blob_tokens: - if len(other) < 5: + if len(other) < 6 or abs(len(other) - len(token)) > 3: continue limit = min(len(token), len(other)) shared = 0 while shared < limit and token[shared] == other[shared]: shared += 1 - if shared >= 5: + if shared >= 6: return True return False @@ -471,7 +474,9 @@ def score_job(job: Job, tokens: list[str]) -> float: for token in tokens: if token in blob_tokens: score += 2.0 - elif token in blob: + elif any(other.startswith(token) for other in blob_tokens): + # word-prefix tier: 'cashi' -> cashier, 'hand' -> handler. A raw + # substring test let 'care' light up every Healthcare posting. score += 1.0 elif _stem_match(token, blob_tokens): score += 0.5 @@ -928,8 +933,11 @@ def apply_submitted(job_id: str): @app.route("/careers-areas/<slug>") def career_area(slug: str): area = Area.query.filter(db.func.lower(Area.slug) == slug.lower()).first() - if area is None or not area.has_index_page: + if area is None: abort(404) + if not area.has_index_page: + # Students has no index page upstream either; send it to its open roles. + return redirect(url_for("results", area=area.slug)) categories = ( Category.query.filter_by(area_id=area.id) .order_by(Category.display_order) diff --git a/sites/walmart_careers/catalog_source.py b/sites/walmart_careers/catalog_source.py index 85ae8e7c..3e78e509 100644 --- a/sites/walmart_careers/catalog_source.py +++ b/sites/walmart_careers/catalog_source.py @@ -140,6 +140,7 @@ ("5260", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5260", "1400 SE Walton Blvd", "Bentonville", "AR", "72712-6220", 36.354900, -94.202500, False, False, "Walmart"), ("144", "WM Supercenter", "WM SUPERCENTER #144", "2110 W Walnut St", "Rogers", "AR", "72756-3611", 36.334100, -94.152800, False, False, "Walmart"), ("8259", "Sam's Club", "SAM'S CLUB #8259", "1101 SE Walton Blvd", "Bentonville", "AR", "72712-6191", 36.357800, -94.199200, False, False, "Sam's Club"), + ("8155", "Sam's Club", "SAM'S CLUB #8155", "3081 N College Ave", "Fayetteville", "AR", "72703-5100", 36.101000, -94.158000, False, False, "Sam's Club"), # --- California -------------------------------------------------------- ("9054", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9054", "1290 W Henderson Ave", "Porterville", "CA", "93257-5969", 36.070300, -119.041800, False, False, "Walmart"), ("2050", "WM Supercenter", "WM SUPERCENTER #2050", "3680 W Shaw Ave", "Fresno", "CA", "93711-3204", 36.808900, -119.828600, False, False, "Walmart"), @@ -151,6 +152,7 @@ ("9399", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9399", "3401 Quincy St", "Plainview", "TX", "79072-3308", 34.164300, -101.700900, False, False, "Walmart"), ("4750", "Sam's Club", "SAM'S CLUB #4750", "3000 E Plano Pkwy", "Plano", "TX", "75074-7440", 33.017200, -96.671900, False, False, "Sam's Club"), ("471", "WM Supercenter", "WM SUPERCENTER #471", "4215 Canyon Dr", "Amarillo", "TX", "79110-1109", 35.166900, -101.850700, False, False, "Walmart"), + ("3826", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #3826", "1521 N Cockrell Hill Rd", "Dallas", "TX", "75211-7407", 32.779000, -96.887000, False, False, "Walmart"), # --- Florida ----------------------------------------------------------- ("3387", "WM Supercenter", "WM SUPERCENTER #3387", "17000 Toledo Blade Blvd", "North Port", "FL", "34287-7281", 27.056300, -82.183200, False, False, "Walmart"), ("6318", "Sam's Club", "SAM'S CLUB #6318", "4763 Millenia Plaza Way", "Orlando", "FL", "32839-6014", 28.485600, -81.430200, False, False, "Sam's Club"), @@ -160,6 +162,9 @@ ("5388", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5388", "6594 Ridge Rd", "Parma", "OH", "44129-5546", 41.387000, -81.748000, False, False, "Walmart"), ("6636", "Sam's Club", "SAM'S CLUB #6636", "3950 W Dublin Granville Rd", "Columbus", "OH", "43235-2701", 40.098700, -83.083100, False, False, "Sam's Club"), ("5439", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5439", "5821 W Central Ave", "Toledo", "OH", "43615-2159", 41.673900, -83.673400, False, False, "Walmart"), + ("2075", "WM Supercenter", "WM SUPERCENTER #2075", "8585 Pearl Rd", "Strongsville", "OH", "44136-1618", 41.314000, -81.829000, False, False, "Walmart"), + ("5133", "WM Supercenter", "WM SUPERCENTER #5133", "24801 Brookpark Rd", "North Olmsted", "OH", "44070-3407", 41.429000, -81.916000, False, False, "Walmart"), + ("4744", "Sam's Club", "SAM'S CLUB #4744", "3560 Steelyard Dr", "Cleveland", "OH", "44109-2101", 41.458600, -81.688900, False, False, "Sam's Club"), # --- New York ---------------------------------------------------------- ("9046", "eComm Whse Logistics", "ECOMM WHSE LOGISTICS #9046", "8827 Old River Rd", "Marcy", "NY", "13403-3030", 43.173965, -75.315183, False, False, "Walmart"), ("6038", "Regional DC", "REGIONAL DISTRIBUTION CENTER #6038", "5000 Halsey Rd", "Marcy", "NY", "13403-2317", 43.155900, -75.297400, False, False, "Walmart"), @@ -179,11 +184,13 @@ ("4137", "WM Supercenter", "WM SUPERCENTER #4137", "1965 S Union Ave", "Tacoma", "WA", "98405-1615", 47.242300, -122.484500, False, False, "Walmart"), ("6216", "Sam's Club", "SAM'S CLUB #6216", "9950 N Newport Hwy", "Spokane", "WA", "99218-1240", 47.741400, -117.400600, False, False, "Sam's Club"), ("5382", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #5382", "8102 Evergreen Way", "Everett", "WA", "98203-6428", 47.905400, -122.229900, False, False, "Walmart"), + ("7021", "Regional DC", "REGIONAL DISTRIBUTION CENTER #7021", "1300 Wine Country Rd", "Grandview", "WA", "98930-9704", 46.254000, -119.901000, False, False, "Walmart"), # --- Puerto Rico ------------------------------------------------------- ("2503", "WM Supercenter", "WM SUPERCENTER #2503", "Carr 2 KM 11.4", "Bayamon", "PR", "00959-5100", 18.394200, -66.155300, False, False, "Walmart"), ("2610", "WM Supercenter", "WM SUPERCENTER #2610", "500 Ave Rafael Cordero", "Caguas", "PR", "00725-3607", 18.245600, -66.036200, False, False, "Walmart"), ("3512", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #3512", "2000 Ave Las Americas", "Ponce", "PR", "00717-0777", 18.019800, -66.612600, False, False, "Walmart"), ("8763", "Sam's Club", "SAM'S CLUB #8763", "100 Ave Fragoso", "Carolina", "PR", "00979-1234", 18.417400, -65.977300, False, False, "Sam's Club"), + ("3593", "Neighborhood Market", "WM NEIGHBORHOOD MARKET #3593", "65 Ave De Diego", "San Juan", "PR", "00927-3300", 18.398500, -66.055300, False, False, "Walmart"), # --- Virginia ---------------------------------------------------------- ("1399", "WM Supercenter", "WM SUPERCENTER #1399", "1123 E Lynchburg Salem Tpke", "Bedford", "VA", "24523-3446", 37.323200, -79.502400, False, False, "Walmart"), ("6088", "Import", "IMPORT DISTRIBUTION CENTER #6088", "8109 Merrimac Trail", "Williamsburg", "VA", "23185-6255", 37.288600, -76.664900, False, False, "Walmart"), @@ -252,6 +259,7 @@ ("6038", "Full time", "WE", 19.75, 23.25, 2, {"shift_time": "Shift may start between 3:00pm - 7:30pm"}), ("9281", "Part time", "WD", 18.90, 22.40, 3, None), ("6014", "Part time", "SD", 19.40, 22.90, 5, None), + ("7021", "Full time", "WN", 20.50, 24.00, 3, None), ], }, { @@ -305,6 +313,7 @@ ("7133", "Part time", "SE,FX", 18.40, 21.90, 2, None), ("6038", "Part time", "WD,FX", 20.10, 23.60, 3, None), ("9399", "Full time", "SN,FX", 19.85, 23.35, 2, None), + ("7021", "Part time", "SD,FX", 19.30, 22.80, 2, None), ], }, { @@ -410,6 +419,7 @@ ("6038", "Full time", "SD", 27.00, 34.50, 2, None), ("7133", "Full time", "WE,FX", 26.00, 33.50, 3, None), ("6014", "Part time", "FX", 25.25, 32.75, 1, None), + ("7021", "Full time", "WD,FX", 26.25, 33.75, 1, None), ], }, { @@ -512,9 +522,12 @@ ("2503", "Part time", "WD,SD", 15.00, 24.00, 5, None), ("2610", "Full time", "WD,WE,SD", 15.00, 24.00, 3, None), ("3512", "Part time", "SE,SN", 15.00, 23.00, 4, None), - ("8763", "Part time", "WD,SN", 16.00, 25.00, 2, None), + ("8763", "Part time", "SE,SN", 16.00, 25.00, 2, None), ("2110", "Full time", "WD,WE", 15.50, 26.00, 4, None), ("5382", "Part time", "WE,SE", 17.00, 28.00, 3, None), + ("3593", "Part time", "WE,SN", 15.00, 23.50, 4, None), + ("2075", "Part time", "WE,SE", 15.50, 25.50, 2, None), + ("3826", "Part time", "WE,SE", 15.00, 24.50, 2, None), ], }, { @@ -564,6 +577,8 @@ ("8259", "Full time", "WD,SD", 18.00, 26.00, 2, None), ("6318", "Part time", "WE,SE", 16.00, 24.00, 3, None), ("8763", "Full time", "WD,WE", 16.00, 24.00, 1, None), + ("4744", "Part time", "WE,SE", 16.50, 24.50, 3, None), + ("8155", "Full time", "WD,SD", 17.00, 25.00, 2, None), ], }, { @@ -591,6 +606,7 @@ ("471", "Full time", "WD,SD", 15.00, 25.00, 4, None), ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, {"job_id": "CP-1236-10888"}), ("5388", "Full time", "WN", 16.50, 27.00, 2, None), + ("5133", "Full time", "WE,SN", 16.00, 26.50, 2, None), ], }, { @@ -616,6 +632,7 @@ ("6608", "Full time", "WD,WN", 19.00, 27.00, 1, None), ("4750", "Part time", "SN,WN", 17.50, 25.50, 2, None), ("8253", "Full time", "WN,SN", 17.00, 25.00, 1, None), + ("8155", "Part time", "WN,SN", 17.50, 25.50, 2, None), ], }, { @@ -641,6 +658,7 @@ ("3387", "Part time", "WD,SD", 15.00, 25.00, 2, None), ("1179", "Full time", "WE,SE,WN", 15.00, 25.00, 4, None), ("954", "Part time", "WD,WE", 15.00, 24.00, 2, None), + ("5133", "Part time", "SD,SE", 15.50, 25.50, 3, None), ], }, { @@ -664,6 +682,8 @@ "placements": [ ("1230", "Full time", "WN", 15.50, 24.50, 3, None), ("2163", "Part time", "SN,WN", 16.50, 26.50, 2, None), + ("2075", "Full time", "WD,SD", 16.00, 25.00, 3, None), + ("3593", "Part time", "WN", 15.00, 24.00, 2, None), ], }, { @@ -718,6 +738,9 @@ ("5991", "Part time", "WE,SE", 17.50, 28.00, 4, None), ("5260", "Full time", "WD,WE,SE", 15.00, 28.00, 2, None), ("5388", "Part time", "WE,SE", 16.00, 27.00, 2, None), + ("2075", "Full time", "WD,WE", 15.50, 26.50, 3, None), + ("3826", "Full time", "WD,SD", 15.00, 26.00, 2, None), + ("3593", "Full time", "WD,SE", 14.50, 25.00, 4, None), ], }, { @@ -768,6 +791,7 @@ ("8259", "Part time", "WD,SD", 16.00, 23.00, 1, None), ("6318", "Part time", "SD,SE,FX", 15.50, 22.50, 3, None), ("6636", "Part time", "WD,SD", 16.00, 23.00, 2, None), + ("4744", "Full time", "WD,SD", 16.50, 23.50, 2, None), ], }, { @@ -870,6 +894,7 @@ ("1230", "Full time", "WD,WE", 17.00, 30.00, 5, None), ("471", "Part time", "WE,SE", 16.50, 29.00, 2, None), ("1179", "Full time", "WD,SD", 17.50, 30.50, 1, None), + ("5133", "Full time", "WD,SD", 17.50, 30.50, 2, None), ], }, { @@ -997,6 +1022,7 @@ ("4137", "Part time", "WE,SE", 19.50, 32.00, 1, None), ("2110", "Full time", "WD,WE", 18.50, 30.50, 1, None), ("2050", "Part time", "SD,SE", 20.00, 33.00, 2, None), + ("3826", "Full time", "WD,WE", 18.50, 31.00, 3, None), ], }, { @@ -1047,6 +1073,8 @@ ("5382", "Part time", "WE,SE", 23.00, 36.00, 1, None), ("1236", "Full time", "WD,WE", 21.00, 34.00, 1, None), ("5991", "Full time", "WD,WE,SD", 22.00, 35.00, 2, None), + ("5133", "Full time", "WD,SD", 21.50, 34.50, 1, None), + ("3826", "Part time", "WE,SE", 22.50, 35.50, 1, None), ], }, { @@ -1177,6 +1205,8 @@ ("6318", "Intern", "WE,FX", 16.50, 20.50, 1, None), ("8259", "Intern", "WD,SD", 17.25, 21.25, 1, None), ("6216", "Intern", "WE,FX", 17.75, 21.75, 1, None), + ("4744", "Intern", "WD,FX", 17.00, 21.00, 1, None), + ("8155", "Intern", "WE,FX", 17.50, 21.50, 1, None), ], }, ] @@ -2001,7 +2031,7 @@ "Dallas", "Our Dallas office anchors merchandising, finance and supply chain teams in the middle of one " "of the fastest-growing metros in the country.", - "loc-dc-metro.jpg", + "loc-dallas.jpg", ), } diff --git a/sites/walmart_careers/seed_data.py b/sites/walmart_careers/seed_data.py index 73b9c5a3..319e6122 100644 --- a/sites/walmart_careers/seed_data.py +++ b/sites/walmart_careers/seed_data.py @@ -58,8 +58,11 @@ # (user email, job title, store number) — resolved to job ids after the catalog is built. SEED_SAVED_JOBS = [ ("alice.j@test.com", "Freight Handler", "9046", datetime(2026, 8, 3, 14, 12, 0)), + ("alice.j@test.com", "Cosmetics Cashier", "2503", datetime(2026, 8, 7, 19, 41, 0)), ("alice.j@test.com", "Optician", "5991", datetime(2026, 8, 9, 10, 5, 0)), - ("alice.j@test.com", "Cosmetics Cashier", "2503", datetime(2026, 8, 17, 19, 41, 0)), + ("alice.j@test.com", "Automation Technician", "6088", datetime(2026, 8, 13, 8, 16, 0)), + ("alice.j@test.com", "Senior UX Designer", "11807", datetime(2026, 8, 17, 12, 27, 0)), + ("alice.j@test.com", "Team Lead", "4137", datetime(2026, 8, 22, 17, 58, 0)), ("bob.c@test.com", "Asset Protection Associate", "5991", datetime(2026, 8, 4, 8, 22, 0)), ("bob.c@test.com", "Class A CDL Truck Driver", "6038", datetime(2026, 8, 11, 16, 48, 0)), ("bob.c@test.com", "Senior Data Scientist", "11500", datetime(2026, 8, 20, 12, 3, 0)), diff --git a/sites/walmart_careers/static/css/site.css b/sites/walmart_careers/static/css/site.css index fdeda27a..42ced8fb 100644 --- a/sites/walmart_careers/static/css/site.css +++ b/sites/walmart_careers/static/css/site.css @@ -12,7 +12,10 @@ --ld-blue-160: #001e60; --ld-blue-10: #e6f1fc; --ld-blue-9: #e9f1fe; + --ld-sky: #a9ddf7; + --ld-sky-60: #4dbdf5; --ld-spark-100: #ffc220; + --ld-spark-30: #ffe4a3; --ld-gray-200: #f1f1f2; --ld-gray-20: #e3e4e5; --ld-gray-5: #f8f8f8; @@ -22,6 +25,7 @@ --ld-green: #2a8703; --ld-sams: #00358e; --header-h: 80px; + --gutter: 140px; } * { box-sizing: border-box; } @@ -48,9 +52,10 @@ img { max-width: 100%; } } .skip-link:focus { left: 8px; top: 8px; } -.wrap { max-width: 1360px; margin: 0 auto; padding: 0 32px; } +.wrap { max-width: 1440px; margin: 0 auto; padding: 0 32px; } +.l1 .wrap { padding: 0 var(--gutter); } .wrap-narrow { max-width: 900px; margin: 0 auto; padding: 0 32px; } -.wrap-bleed { max-width: none; padding: 0 24px; } +.wrap-bleed { max-width: 1440px; margin: 0 auto; padding: 0 24px; } /* ------------------------------ header ---------------------------------- */ .site-header { @@ -89,9 +94,14 @@ img { max-width: 100%; } } .nav-pop a:hover, .nav-pop .linkish:hover { background: var(--ld-blue-9); color: var(--ld-blue-160); } .nav-pop hr { border: 0; border-top: 1px solid var(--ld-gray-20); margin: 8px 12px; width: calc(100% - 24px); } -.nav-pop .lang { padding: 9px 16px; font-size: 14px; color: var(--ld-text-subtle); } +.nav-pop .lang { + padding: 9px 16px; font-size: 14px; color: var(--ld-blue-160); + display: inline-flex; align-items: center; gap: 8px; +} +.nav-pop .lang svg { color: var(--ld-blue-100); } .header-search { display: flex; align-items: center; flex: 1 1 auto; justify-content: flex-end; } +.header-spacer { flex: 1 1 auto; } .header-search form { display: flex; align-items: center; background: #fff; border-radius: 999px; padding: 5px 5px 5px 22px; @@ -116,7 +126,8 @@ img { max-width: 100%; } .user-menu > summary::-webkit-details-marker { display: none; } .user-menu > summary::marker { content: ""; } .user-menu[open] > summary, .user-menu > summary:hover { background: var(--ld-blue-130); } -.user-menu .nav-pop { left: auto; right: 0; } +.user-menu .nav-pop { left: auto; right: 0; min-width: 128px; padding: 14px 8px; top: calc(100% - 8px); } +.user-menu .nav-pop a, .user-menu .nav-pop .linkish { font-size: 14px; padding: 8px 12px; } .avatar { width: 34px; height: 34px; border-radius: 999px; background: var(--ld-spark-100); color: var(--ld-blue-160); display: grid; place-items: center; font-weight: 700; font-size: 14px; @@ -129,7 +140,7 @@ img { max-width: 100%; } /* ------------------------------ buttons --------------------------------- */ .btn { display: inline-block; border: 0; border-radius: 999px; cursor: pointer; - font: inherit; font-weight: 600; padding: 12px 28px; text-decoration: none; + font: inherit; font-weight: 700; padding: 12px 24px; text-decoration: none; background: var(--ld-blue-100); color: #fff; } .btn:hover { background: var(--ld-blue-130); color: #fff; } @@ -138,50 +149,206 @@ img { max-width: 100%; } .btn-spark { background: var(--ld-spark-100); color: var(--ld-blue-160); } .btn-spark:hover { background: #ffd45c; color: var(--ld-blue-160); } .btn-sm { padding: 7px 18px; font-size: 14px; } +.btn-block { display: block; width: 100%; text-align: center; padding: 14px 24px; } /* ------------------------------ hero ------------------------------------ */ .hero { background: var(--ld-blue-100); color: #fff; padding: 0; position: relative; } -.hero .inner { padding: 96px 0 168px; text-align: center; } -.hero h1 { font-size: 62px; line-height: 1.08; font-weight: 400; margin: 0 auto 42px; max-width: 900px; } +.hero .inner { max-width: 932px; margin: 0 auto; padding: 84px 0 300px; text-align: left; } +.hero h1 { font-size: 92px; line-height: 1.18; font-weight: 300; margin: 0 0 56px; } .hero h1 span { display: block; } .hero-search { display: flex; align-items: center; background: var(--ld-blue-130); - border-radius: 999px; padding: 10px 10px 10px 34px; max-width: 720px; margin: 0 auto; + border-radius: 999px; padding: 22px 22px 22px 40px; margin: 0; } .hero-search input { - flex: 1; min-width: 0; border: 0; outline: none; font-size: 19px; font-family: inherit; - color: #fff; background: transparent; + flex: 1; min-width: 0; border: 0; outline: none; font-size: 24px; font-family: inherit; + color: #fff; background: transparent; font-weight: 300; } .hero-search input::placeholder { color: #fff; } .hero-search button { border: 0; background: #fff; color: var(--ld-blue-100); border-radius: 999px; - width: 54px; height: 54px; display: grid; place-items: center; cursor: pointer; + width: 68px; height: 68px; display: grid; place-items: center; cursor: pointer; } .hero-strip { - position: absolute; left: 0; right: 0; bottom: -104px; - display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 24px; padding: 0 24px; align-items: end; + position: absolute; left: 0; right: 0; bottom: -232px; + display: grid; grid-template-columns: 330px 1fr 330px; gap: 46px; align-items: end; } .hero-strip > img, .hero-strip-mid img { - display: block; width: 100%; height: 250px; object-fit: cover; border-radius: 20px; + display: block; width: 100%; height: 380px; object-fit: cover; border-radius: 20px; } -.hero-strip > img:first-child { border-radius: 0 20px 20px 0; margin-left: -24px; } -.hero-strip > img:last-child { border-radius: 20px 0 0 20px; margin-right: -24px; } +.hero-strip > img:first-child { border-radius: 0 20px 20px 0; } +.hero-strip > img:last-child { border-radius: 20px 0 0 20px; } .hero-strip-mid { position: relative; } .hero-pill { - position: absolute; left: 50%; transform: translateX(-50%); bottom: 24px; + position: absolute; right: 62px; bottom: 76px; background: var(--ld-blue-100); color: #fff; border-color: var(--ld-blue-100); + font-weight: 400; font-size: 15px; padding: 10px 24px; } .hero-pill:hover { background: var(--ld-blue-130); color: #fff; } -.hero + section { padding-top: 152px; } +.after-hero { padding-top: 400px; } /* ------------------------------ sections -------------------------------- */ section { padding: 56px 0; } -section h2 { font-size: 34px; font-weight: 400; margin: 0 0 28px; } +section h2 { font-size: 40px; font-weight: 300; margin: 0 0 32px; line-height: 1.15; } +section h2.tight { margin-bottom: 12px; } section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } +.section-blurb { font-size: 15px; max-width: 680px; margin: 0 0 40px; } .section-alt { background: var(--ld-gray-5); } .section-blue { background: var(--ld-blue-160); color: #fff; } .section-blue h2, .section-blue a { color: #fff; } +/* career-area ribbon */ +.ribbon-section { padding: 90px 0 60px; } +.ribbon { display: flex; border-radius: 40px; overflow: hidden; } +.ribbon-seg { + flex: 1 1 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; + padding: 44px 30px 44px 24px; text-decoration: none; font-size: 18px; font-weight: 300; + line-height: 1.3; border-radius: 40px 0 0 40px; margin-left: -40px; padding-left: 60px; +} +.ribbon-seg:first-child { margin-left: 0; padding-left: 24px; } +.ribbon-seg svg { flex: 0 0 auto; } +.seg-1 { background: var(--ld-blue-10); color: var(--ld-blue-160); z-index: 5; } +.seg-2 { background: var(--ld-sky); color: var(--ld-blue-160); z-index: 4; } +.seg-3 { background: var(--ld-blue-100); color: #fff; z-index: 3; } +.seg-4 { background: var(--ld-blue-130); color: #fff; z-index: 2; } +.seg-5 { background: var(--ld-blue-160); color: #fff; z-index: 1; } +.ribbon-seg:hover { filter: brightness(1.06); color: inherit; } + +/* bento */ +.bento-section { padding: 40px 0 70px; } +.bento-layout { display: grid; grid-template-columns: 1040px 1fr; gap: 24px; align-items: start; } +.bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 24px; } +.tile { + position: relative; border-radius: 24px; overflow: hidden; text-decoration: none; + display: block; color: var(--ld-blue-160); +} +.tile-sq { grid-column: span 2; height: 296px; } +.tile-wide { grid-column: span 4; height: 296px; } +.tile-short { grid-column: span 2; height: 150px; } +.tile-mini { grid-column: span 1; height: 150px; } +.tile-gap { grid-column: span 2; } +.tile-sky { background: var(--ld-sky); } +.tile-pale { background: var(--ld-blue-10); } +.tile-blue { background: var(--ld-blue-100); color: #fff; } +.tile-navy { background: var(--ld-blue-160); color: #fff; } +.tile-spark { background: var(--ld-spark-100); } +.tile-photo img { display: block; width: 100%; height: 100%; object-fit: cover; } +.tile-center { display: grid; place-items: center; } +.tile-label { position: absolute; left: 24px; top: 22px; right: 24px; font-size: 26px; font-weight: 300; line-height: 1.25; } +.tile-spark { width: 130px; height: 130px; } +.tile-blue .tile-spark { width: 130px; } +.tile-heart { width: 86px; height: 86px; } +.tile-diamond { width: 140px; height: 140px; filter: brightness(0) invert(1); } +.circle-arrow { + position: absolute; left: 24px; bottom: 22px; width: 48px; height: 48px; border-radius: 999px; + border: 1px solid currentColor; display: grid; place-items: center; font-size: 30px; line-height: 1; + font-weight: 300; +} +a.tile:hover { color: inherit; filter: brightness(1.04); } +a.tile.tile-sky:hover, a.tile.tile-pale:hover { color: var(--ld-blue-160); } +.bento-aside { padding-top: 12px; } +.bento-aside h2 { font-size: 40px; font-weight: 300; margin: 0 0 22px; } + +/* benefits */ +.benefits-layout { display: grid; grid-template-columns: 1fr 360px; gap: 60px; align-items: start; } +.benefit-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 34px 40px; } +.benefit-grid.two-col { grid-template-columns: repeat(2, 1fr); max-width: 760px; margin-top: 30px; } +.benefit-item img { width: 60px; height: 60px; display: block; margin-bottom: 10px; } +.benefit-item b { display: block; font-size: 13px; margin-bottom: 4px; } +.benefit-item span { font-size: 13px; color: var(--ld-blue-160); display: block; line-height: 1.45; } +.benefits-aside p { font-size: 30px; font-weight: 300; line-height: 1.35; margin: 0 0 30px; } +.benefit-list { display: grid; gap: 4px; } +.benefit-row { display: flex; gap: 18px; align-items: flex-start; padding: 18px 0; border-bottom: 1px solid var(--ld-gray-20); } +.benefit-row img { width: 40px; height: 40px; } +.benefit-row b { display: block; font-size: 18px; } +.benefit-row span { color: var(--ld-text-subtle); } + +/* milestones */ +.milestones { padding: 90px 0 60px; } +.milestone-layout { display: grid; grid-template-columns: 360px 1fr; gap: 20px; align-items: start; } +.milestone-layout h2 { font-size: 64px; font-weight: 300; line-height: 1.05; margin: 90px 0 0; } +.badge-stack { display: flex; flex-direction: column; gap: 110px; } +.badge-row { display: flex; align-items: flex-start; gap: 24px; } +.badge-row.row-2, .badge-row.row-4 { padding-left: 0; } +.badge-row.row-3 { padding-left: 0; } +.badge { + width: 452px; border-radius: 26px; overflow: hidden; border: 1px solid var(--ld-gray-20); + text-align: center; box-shadow: 0 1px 2px rgba(0, 30, 96, .08); background: #fff; +} +.badge-top { height: 48px; position: relative; } +.badge-top .grip { + position: absolute; left: 50%; top: 8px; transform: translateX(-50%); + width: 40px; height: 7px; border-radius: 999px; background: #fff; +} +.badge-top em { position: absolute; right: 50px; top: 18px; font-style: normal; font-size: 16px; } +.badge-body { padding: 40px 36px; font-size: 24px; line-height: 1.4; min-height: 160px; display: grid; place-items: center; } +.badge-foot { padding: 20px 0 26px; font-weight: 800; color: var(--ld-blue-100); font-size: 24px; letter-spacing: -.5px; } +.badge-foot.plain { font-weight: 400; color: var(--ld-blue-160); font-size: 16px; } +.badge-sky .badge-top { background: var(--ld-sky-60); } +.badge-sky .badge-body { background: var(--ld-blue-100); color: #fff; } +.badge-spark .badge-top { background: var(--ld-spark-100); color: var(--ld-blue-160); } +.badge-spark .badge-body { background: var(--ld-spark-30); } +.badge-navy .badge-top { background: var(--ld-blue-130); } +.badge-navy .badge-body { background: #fff; } +.badge-blue .badge-top { background: var(--ld-blue-100); color: #fff; } +.badge-blue .badge-body { background: var(--ld-blue-160); color: #fff; } +.badge-blank { width: 214px; height: 132px; border-radius: 26px; flex: 0 0 auto; } +.blank-sky { background: var(--ld-sky-60); } +.blank-spark { background: var(--ld-spark-30); } +.blank-navy { background: transparent; } +.blank-blue { background: var(--ld-blue-100); } + +/* video strip */ +.video-strip { display: grid; grid-template-columns: 300px 1fr 300px; gap: 24px; } +.video-tile { position: relative; height: 386px; border-radius: 24px; overflow: hidden; background: var(--ld-blue-100); color: #fff; } +.video-tile > img:first-child { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .22; mix-blend-mode: luminosity; } +.video-strip.dark .video-tile { background: #000; } +.video-strip.dark .video-tile > img:first-child { opacity: .55; mix-blend-mode: normal; } +.v-spark { position: absolute; left: 50%; top: 150px; transform: translateX(-50%); width: 76px; height: 76px; } +.v-cap { position: absolute; left: 24px; bottom: 26px; } +.v-cap em { display: block; font-style: normal; font-size: 14px; } +.v-cap b { display: block; font-size: 20px; margin: 4px 0 18px; } +.play { + display: block; width: 56px; height: 56px; border-radius: 999px; background: #fff; position: relative; +} +.play::after { + content: ""; position: absolute; left: 21px; top: 16px; border-left: 20px solid var(--ld-blue-100); + border-top: 12px solid transparent; border-bottom: 12px solid transparent; +} +.dots { display: flex; justify-content: center; gap: 12px; margin-top: 34px; } +.dots i { width: 12px; height: 12px; border-radius: 999px; border: 1.5px solid var(--ld-blue-160); display: block; } +.dots i.on { width: 100px; background: linear-gradient(90deg, var(--ld-blue-160) 24%, #fff 24%); } + +/* find the role */ +.find-role { padding: 120px 0 130px; } +.find-role h2 { font-size: 90px; font-weight: 300; margin: 0 0 56px; letter-spacing: -.5px; } +.find-search { + display: flex; align-items: center; background: var(--ld-gray-5); border: 1px solid var(--ld-gray-20); + border-radius: 999px; padding: 14px 14px 14px 40px; +} +.find-search input { + flex: 1; min-width: 0; border: 0; outline: none; font-size: 24px; font-family: inherit; + font-weight: 300; color: var(--ld-blue-160); background: transparent; +} +.find-search input::placeholder { color: var(--ld-text-subtle); } +.find-search button { + border: 0; background: var(--ld-blue-100); color: #fff; border-radius: 999px; + width: 68px; height: 68px; display: grid; place-items: center; cursor: pointer; +} + +/* quote band */ +.quote-section { background: var(--ld-blue-100); color: #fff; padding: 160px 0; } +.quote-band-text { max-width: 620px; margin: 0 auto; font-size: 30px; font-weight: 300; line-height: 1.35; } + +.quote-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; padding-top: 110px; } +.quote-card { + border-radius: 16px; background: #fff; box-shadow: 0 6px 24px rgba(0, 30, 96, .14); + padding: 24px 28px 30px; align-self: end; +} +.quote-card.on { transform: translateY(-40px); } +.quote-card b { display: block; font-size: 14px; margin-bottom: 10px; } +.quote-card p { margin: 0; font-size: 17px; line-height: 1.4; } + .carousel { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; } .carousel a { display: block; text-decoration: none; color: var(--ld-blue-160); @@ -193,34 +360,14 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } .carousel .cap em { display: block; font-style: normal; color: var(--ld-text-subtle); font-size: 14px; } .carousel .cap i { display: block; font-style: normal; margin-top: 8px; color: var(--ld-blue-100); font-weight: 600; font-size: 14px; } -.ribbon { display: flex; flex-wrap: wrap; gap: 12px; } -.ribbon a { - background: var(--ld-blue-10); color: var(--ld-blue-160); text-decoration: none; - padding: 14px 26px; border-radius: 999px; font-weight: 600; -} -.ribbon a:hover { background: var(--ld-blue-9); color: var(--ld-blue-130); } - -.benefit-list { display: grid; gap: 4px; } -.benefit-row { display: flex; gap: 18px; align-items: flex-start; padding: 18px 0; border-bottom: 1px solid var(--ld-gray-20); } -.benefit-row img { width: 40px; height: 40px; } -.benefit-row b { display: block; font-size: 18px; } -.benefit-row span { color: var(--ld-text-subtle); } - .stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } .stat-card { background: var(--ld-blue-10); border-radius: 24px; padding: 28px; } .stat-card b { display: block; font-size: 34px; font-weight: 700; color: var(--ld-blue-100); } .tile-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } -.tile { border-radius: 24px; overflow: hidden; background: var(--ld-gray-5); } -.tile img { display: block; width: 100%; height: 200px; object-fit: cover; } -.tile .cap { padding: 16px 18px; } - -.bento { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; align-items: stretch; } -.bento .card { border-radius: 24px; overflow: hidden; background: var(--ld-blue-10); } -.bento .card img { display: block; width: 100%; height: 100%; min-height: 260px; object-fit: cover; } -.bento .values { padding: 28px; } -.bento .values ul { margin: 0; padding-left: 18px; } -.bento .values li { margin-bottom: 10px; } +.tile-grid .tile { border-radius: 24px; overflow: hidden; background: var(--ld-gray-5); } +.tile-grid .tile img { display: block; width: 100%; height: 200px; object-fit: cover; } +.tile-grid .tile .cap { padding: 16px 18px; } /* ------------------------------ job cards ------------------------------- */ .job-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; } @@ -228,18 +375,21 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } .job-grid.three-col { grid-template-columns: repeat(3, minmax(0, 1fr)); } .job-card { border: 1px solid var(--ld-gray-20); border-radius: 24px; padding: 22px 24px 24px; - background: #fff; display: block; + background: #fff; display: block; position: relative; } .job-card:hover { border-color: var(--ld-blue-130); } .job-card .spark { width: 24px; height: 24px; display: block; margin-bottom: 14px; } .job-card h3 { margin: 0 0 10px; font-size: 17px; font-weight: 700; } .job-card h3 a { color: var(--ld-blue-160); text-decoration: none; } .job-card h3 a:hover { text-decoration: underline; } +.job-card .card-link::after { content: ""; position: absolute; inset: 0; border-radius: 24px; } .job-card .meta { font-size: 15px; color: var(--ld-blue-160); } .job-card .meta div { margin-bottom: 2px; } .job-card .pay { margin-top: 6px; font-size: 15px; } -.job-card .actions { margin-top: 16px; display: flex; gap: 10px; align-items: center; } +.job-card .actions { margin-top: 18px; display: flex; gap: 10px; align-items: center; position: relative; z-index: 1; } .job-card .actions form { margin: 0; } +.job-card-compact { padding: 18px 22px 22px; } +.job-card-compact h3 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* the live "Select +" pill */ .pill { @@ -255,6 +405,8 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } /* ------------------------------ results --------------------------------- */ .results-layout { display: grid; grid-template-columns: 372px 1fr; gap: 28px; padding: 28px 0 64px; } +.results-layout { max-width: none; } +.results-page { padding: 0 24px; } .results-aside .map-panel { border-radius: 20px; overflow: hidden; position: sticky; top: 100px; } .cluster-map { display: block; } .pin-card { display: block; border-radius: 16px; } @@ -291,11 +443,6 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } } .tool-btn:hover { background: var(--ld-gray-5); } .tool-btn .caret { font-size: 12px; } -.count-badge { - background: var(--ld-blue-100); color: #fff; border-radius: 999px; min-width: 20px; - height: 20px; display: inline-grid; place-items: center; font-size: 12px; font-weight: 700; - padding: 0 6px; -} .toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 18px; margin: 12px 0 16px; } .active-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 0 0 20px; } .active-chip { @@ -426,6 +573,13 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } .benefit-tile img { width: 44px; height: 44px; margin-bottom: 12px; } .benefit-tile b { display: block; font-size: 20px; } .benefit-tile em { font-style: normal; display: block; color: var(--ld-spark-100); margin-bottom: 10px; } +.benefit-tile p { font-size: 13px; margin: 8px 0 0; line-height: 1.45; } +.benefit-tile.tile-1 { background: var(--ld-blue-100); } +.benefit-tile.tile-1 em { color: #fff; font-weight: 700; } +.benefit-tile.tile-2 { background: var(--ld-sky); color: var(--ld-blue-160); } +.benefit-tile.tile-2 em { color: var(--ld-blue-160); font-weight: 700; } +.benefit-tile.tile-2 img { filter: brightness(0) saturate(100%) invert(9%) sepia(60%) saturate(4000%) hue-rotate(215deg); } +.benefit-tile.tile-3 em { color: #fff; font-weight: 700; } .quote-band { background: var(--ld-blue-100); color: #fff; border-radius: 24px; padding: 32px; font-size: 22px; } @@ -448,6 +602,27 @@ section h3 { font-size: 22px; font-weight: 700; margin: 0 0 12px; } .flash.warning { background: #fff5e0; border: 1px solid var(--ld-spark-100); color: #7a5a00; } .form-note { font-size: 14px; color: var(--ld-text-subtle); margin-top: 14px; } +/* stripped sign-in / register layout (identity.walmart.com) */ +.auth-body { background: #fff; min-height: 100vh; display: flex; flex-direction: column; } +.auth-main { flex: 1 0 auto; max-width: 352px; margin: 0 auto; padding: 28px 0 60px; width: 100%; } +.auth-spark { display: block; width: 56px; margin: 0 auto 20px; } +.auth-spark img { display: block; width: 56px; height: 56px; } +.auth-card h1 { font-size: 20px; font-weight: 700; text-align: center; margin: 0 0 22px; } +.auth-lead { text-align: center; font-size: 17px; margin: 0 0 26px; line-height: 1.4; } +.auth-card .field label { font-size: 14px; font-weight: 700; } +.auth-card .field input { + background: #fff; border: 1px solid var(--ld-blue-160); border-radius: 8px; padding: 16px 14px; +} +.auth-card .form-note { text-align: center; } +.auth-flash { margin: 0 0 18px; } +.auth-footer { + border-top: 1px solid var(--ld-gray-20); padding: 24px 80px 40px; display: flex; gap: 24px; + align-items: center; justify-content: space-between; font-size: 13px; color: var(--ld-blue-160); +} +.auth-footer nav { display: flex; gap: 24px; flex-wrap: wrap; } +.auth-footer a { color: var(--ld-blue-160); text-decoration: none; font-size: 14px; } +.auth-footer a:hover { text-decoration: underline; } + .review-list { list-style: none; margin: 0 0 22px; padding: 0; } .review-list li { display: flex; justify-content: space-between; gap: 20px; padding: 10px 0; border-bottom: 1px solid var(--ld-gray-20); } .review-list b { font-weight: 600; } @@ -461,27 +636,74 @@ table.data th, table.data td { text-align: left; padding: 12px 10px; border-bott table.data th { font-size: 14px; text-transform: uppercase; letter-spacing: .04em; color: var(--ld-text-subtle); } /* ------------------------------ area / locations ------------------------ */ -.area-hero { position: relative; color: #fff; } -.area-hero img { width: 100%; height: 360px; object-fit: cover; display: block; } -.area-hero .overlay { - position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0,30,96,.88), rgba(0,30,96,.32)); - display: flex; align-items: center; -} -.area-hero h1 { font-size: 46px; font-weight: 400; margin: 0 0 14px; } -.area-hero p { max-width: 620px; font-size: 18px; margin: 0 0 22px; } +.area-hero { position: relative; } +.area-hero img { width: 100%; height: 780px; object-fit: cover; display: block; } +.area-card { + position: absolute; left: var(--gutter); top: 250px; width: 700px; background: var(--ld-blue-10); + border-radius: 24px; padding: 40px 30px 30px 16px; color: var(--ld-blue-160); +} +.area-card h1 { font-size: 34px; font-weight: 300; margin: 0 0 24px; } +.area-card p { font-size: 18px; line-height: 1.45; margin: 0 0 24px; } + +.join-grid { display: grid; grid-template-columns: 696px 1fr; gap: 116px; align-items: start; } +.stack-card { position: relative; padding-top: 40px; } +.stack-band { display: block; height: 30px; border-radius: 24px 24px 0 0; } +.band-navy { background: var(--ld-blue-130); position: absolute; top: 0; left: 0; right: 0; height: 60px; } +.band-sky { background: var(--ld-blue-10); position: absolute; top: 36px; left: 0; right: 0; height: 60px; } +.stack-card img { display: block; width: 100%; height: 450px; object-fit: cover; border-radius: 24px; position: relative; margin-top: 36px; } +.stack-controls { + position: absolute; left: 0; right: 0; bottom: 60px; display: flex; justify-content: center; gap: 22px; +} +.stack-controls i { width: 22px; height: 22px; border-radius: 999px; border: 1px solid #fff; color: #fff; font-style: normal; font-size: 12px; display: grid; place-items: center; } +.category-list { list-style: none; margin: 0; padding: 0; } +.category-list li { margin: 0 0 26px; } +.category-list a { font-size: 24px; font-weight: 300; text-decoration: none; color: var(--ld-blue-160); line-height: 1.3; } +.category-list a:hover { text-decoration: underline; color: var(--ld-blue-100); } .category-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } -.category-grid a { - display: flex; justify-content: space-between; gap: 12px; text-decoration: none; - border: 1px solid var(--ld-gray-20); border-radius: 16px; padding: 18px 22px; color: var(--ld-blue-160); + +.hub-links { display: flex; justify-content: space-between; gap: 24px; padding: 60px 0 0; } +.hub-links a { + display: inline-flex; align-items: center; gap: 16px; text-decoration: none; color: var(--ld-blue-160); + font-size: 18px; font-weight: 300; } -.category-grid a:hover { border-color: var(--ld-blue-130); background: var(--ld-gray-5); } -.category-grid .count { color: var(--ld-text-subtle); font-size: 15px; } +.hub-links a:hover { color: var(--ld-blue-100); } .hub-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; } .hub-card { border: 1px solid var(--ld-gray-20); border-radius: 24px; overflow: hidden; } .hub-card img { display: block; width: 100%; height: 220px; object-fit: cover; } .hub-card .cap { padding: 22px; } +.loc-hero { position: relative; border-radius: 24px; overflow: hidden; margin-top: 0; } +.loc-hero img { display: block; width: 100%; height: 480px; object-fit: cover; } +.loc-hero h1 { + position: absolute; left: 60px; bottom: 60px; margin: 0; color: #fff; font-size: 72px; font-weight: 300; + text-shadow: 0 2px 12px rgba(0, 0, 0, .35); +} +.loc-hero .pause { + position: absolute; right: 40px; top: 40px; width: 48px; height: 48px; border-radius: 999px; + border: 1px solid #fff; color: #fff; display: grid; place-items: center; font-size: 16px; +} +.loc-intro { padding: 54px 0 20px; } +.loc-intro p { font-size: 22px; font-weight: 300; max-width: 720px; margin: 0; line-height: 1.35; } +.hub-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 34px 28px; } +.hub-tile img { display: block; width: 100%; height: 202px; object-fit: cover; border-radius: 16px; margin-bottom: 14px; } +.hub-tile b { display: block; font-size: 14px; margin-bottom: 8px; } +.hub-tile p { font-size: 13px; margin: 0 0 24px; line-height: 1.45; } +.arrow-link { display: inline-flex; color: var(--ld-blue-160); } +.arrow-link:hover { color: var(--ld-blue-100); } + +/* saved roles promo */ +.promo-section { padding: 80px 0 60px; } +.promo { display: grid; grid-template-columns: 1fr 565px; gap: 40px; align-items: start; } +.promo h1 { font-size: 64px; font-weight: 300; line-height: 1.12; margin: 0 0 20px; } +.promo p { font-size: 17px; max-width: 560px; margin: 0 0 32px; } +.promo-photo { display: block; width: 565px; height: 376px; object-fit: cover; border-radius: 24px; } +.saved-section { padding: 40px 0 20px; } +.saved-heading { font-size: 48px; font-weight: 300; margin: 0 0 28px; } +.saved-note { font-size: 18px; max-width: 420px; margin: 0 0 40px; line-height: 1.4; } +.trending-section { padding: 20px 0 60px; } +.trending-heading { font-size: 22px; font-weight: 400; margin: 0 0 20px; } + .faq details { border-bottom: 1px solid var(--ld-gray-20); padding: 14px 0; } .faq summary { cursor: pointer; font-weight: 600; font-size: 17px; } .faq p { margin: 10px 0 0; color: var(--ld-text-subtle); } @@ -490,29 +712,41 @@ table.data th { font-size: 14px; text-transform: uppercase; letter-spacing: .04e /* ------------------------------ footer ---------------------------------- */ .site-footer { background: var(--ld-blue-160); color: #fff; padding: 72px 0 40px; margin-top: 40px; } +.site-footer .wrap { padding: 0 70px; } .site-footer a { color: #fff; text-decoration: none; } .site-footer a:hover { text-decoration: underline; color: #fff; } -.footer-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; margin-bottom: 40px; } -.footer-cols h4 { font-size: 17px; margin: 0 0 14px; } +.footer-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; margin-bottom: 40px; max-width: 760px; } +.footer-cols h4 { font-size: 22px; font-weight: 400; margin: 0 0 14px; } .footer-cols ul { list-style: none; margin: 0; padding: 0; } -.footer-cols li { margin-bottom: 9px; font-size: 15px; } +.footer-cols li { margin-bottom: 9px; font-size: 14px; } .social { display: flex; gap: 16px; margin-bottom: 28px; } .social img { width: 26px; height: 26px; } -.legal-text { font-size: 13px; color: #cfd9ea; margin-bottom: 16px; } -.footer-bottom { display: flex; gap: 22px; flex-wrap: wrap; font-size: 14px; border-top: 1px solid rgba(255,255,255,.2); padding-top: 22px; } +.legal-text { font-size: 12px; color: #cfd9ea; margin-bottom: 16px; } +.footer-bottom { display: flex; gap: 22px; flex-wrap: wrap; font-size: 12px; padding-top: 10px; } -@media (max-width: 1100px) { +@media (max-width: 1200px) { + :root { --gutter: 32px; } .results-layout, .detail-layout { grid-template-columns: 1fr; } .carousel, .stat-grid, .tile-grid, .steps, .footer-cols { grid-template-columns: repeat(2, 1fr); } - .job-grid, .job-grid.three-col, .category-grid, .benefit-tiles, .hub-grid, .bento { grid-template-columns: 1fr; } + .job-grid, .job-grid.three-col, .category-grid, .benefit-tiles, .hub-grid, .hub-tiles, + .promo, .join-grid, .bento-layout, .benefits-layout, .milestone-layout { grid-template-columns: 1fr; } + .bento { grid-template-columns: repeat(2, 1fr); } + .tile-sq, .tile-wide, .tile-short, .tile-mini, .tile-gap { grid-column: span 1; } .hero-strip { position: static; grid-template-columns: 1fr; padding: 0; } - .hero .inner { padding: 56px 0; } - .hero + section { padding-top: 56px; } + .hero .inner { padding: 56px 32px; } + .after-hero { padding-top: 56px; } .hero-grid { grid-template-columns: 1fr; } .fact-row, .chips { grid-template-columns: 1fr; } .filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .pop-panel, .pop-panel.wide { min-width: 300px; } .detail-nav, .fact-card, .results-aside .map-panel { position: static; } .detail-nav { margin-left: 0; border-radius: 24px; } - .hero h1 { font-size: 40px; } + .hero h1, .find-role h2 { font-size: 40px; } + .area-card { position: static; width: auto; margin: -80px 32px 0; } + .area-hero img { height: 480px; } + .video-strip { grid-template-columns: 1fr; } + .badge, .promo-photo { width: 100%; } + .milestone-layout h2 { margin-top: 0; } + .ribbon { flex-direction: column; } + .ribbon-seg, .ribbon-seg:first-child { margin-left: 0; padding-left: 24px; border-radius: 0; } } diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl index a69b1272..11b80493 100644 --- a/sites/walmart_careers/tasks.jsonl +++ b/sites/walmart_careers/tasks.jsonl @@ -2,7 +2,7 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--1", "ques": "Find the Staff, Software Engineer posting located in Sunnyvale, CA and quote the exact text of \"Option 2\" under Minimum Qualifications.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/R-2463275"} {"web_name": "Walmart Careers", "id": "Walmart Careers--2", "ques": "Open the Freight Handler posting at eComm Whse Logistics #9054 in Porterville, CA. What shift start window does it list, and how many open positions does it show?", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9054-11013"} {"web_name": "Walmart Careers", "id": "Walmart Careers--3", "ques": "From the Healthcare career area page, go to its open roles and open the Pharmacy Technician posting in Bentonville, AR. What hashtag appears at the end of the \"What you'll bring\" section, and how many open positions does the posting list?", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/healthcare"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift paying no more than $20.00/hr. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--4", "ques": "Using the filters, find Sam's Club Part time roles with a Weekend Overnight shift whose posted pay range tops out at $20.00/hr or less. Open the one in Texas and report its requisition ID and number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} {"web_name": "Walmart Careers", "id": "Walmart Careers--5", "ques": "Find Full time Technology roles in Hoboken, NJ whose salary range tops out above $200,000. Open the matching posting and report its requisition ID and the degree named in \"Option 1\" of its Minimum Qualifications.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All&careerareas=Technology"} {"web_name": "Walmart Careers", "id": "Walmart Careers--6", "ques": "Set your location to Cleveland, OH within 25 miles, filter to Full time roles on a Weekday Day shift, and open the Online Order Filling Team Supervisor posting. Report the street address and the number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} {"web_name": "Walmart Careers", "id": "Walmart Careers--7", "ques": "Filter to the Students career area, the Intern employment type and the Sam's Club brand, then open the merchandising internship in Bentonville, AR. Report the worker type chip shown on the posting and the street address listed for its location.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=All"} @@ -13,7 +13,7 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} {"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR, then open My applications and report the confirmation number of your existing application.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} {"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} {"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} diff --git a/sites/walmart_careers/templates/_job_card.html b/sites/walmart_careers/templates/_job_card.html index 16b8cc08..ad3b2eaf 100644 --- a/sites/walmart_careers/templates/_job_card.html +++ b/sites/walmart_careers/templates/_job_card.html @@ -3,14 +3,18 @@ title, "City, ST" and the annual pay range; an hourly posting adds the "banner #store" line, the ZIP and the shift label. Everything else (street, requisition ID, open positions, shift window, qualifications) is detail-only. + + The whole card is clickable (stretched title link) and carries one outlined + "Select +" pill, exactly like upstream. `compact` drops the pill (trending + strips); `unsave` adds the "Saved" toggle used on the saved-roles page. #} -{% macro job_card(job, saved_ids) -%} -<article class="job-card"> +{% macro job_card(job, saved_ids, compact=False, unsave=False) -%} +<article class="job-card{% if compact %} job-card-compact{% endif %}"> <img class="spark" src="{{ url_for('static', filename='icons/' ~ ('sams-spark.svg' if job.brand == "Sam's Club" else 'spark-yellow-card.svg')) }}" alt="{{ job.brand }}"> <div class="body"> - <h3><a href="{{ url_for('job_detail', job_id=job.job_id) }}">{{ job.title }}</a></h3> + <h3><a class="card-link" href="{{ url_for('job_detail', job_id=job.job_id) }}">{{ job.title }}</a></h3> <div class="meta"> {% if job.is_salaried %} <div>{{ job.store.city }}, {{ job.store.state }}</div> @@ -25,22 +29,18 @@ <h3><a href="{{ url_for('job_detail', job_id=job.job_id) }}">{{ job.title }}</a> {%- if not job.is_salaried %}{{ job.shift_label }} • {% endif -%} {{ job.pay_range }} </div> + {% if not compact %} <div class="actions"> - <a class="pill" href="{{ url_for('job_detail', job_id=job.job_id) }}">View role <span>+</span></a> - {% if job.job_id in saved_ids %} + <a class="pill" href="{{ url_for('job_detail', job_id=job.job_id) }}">Select <span>+</span></a> + {% if unsave %} <form method="post" action="{{ url_for('unsave_job', job_id=job.job_id) }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="next" value="{{ request.full_path }}"> <button type="submit" class="pill pill-on">Saved <span>✓</span></button> </form> - {% else %} - <form method="post" action="{{ url_for('save_job', job_id=job.job_id) }}"> - <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> - <input type="hidden" name="next" value="{{ request.full_path }}"> - <button type="submit" class="pill">Save role <span>+</span></button> - </form> {% endif %} </div> + {% endif %} </div> </article> {%- endmacro %} diff --git a/sites/walmart_careers/templates/area.html b/sites/walmart_careers/templates/area.html index 9b59eaf5..e3451c03 100644 --- a/sites/walmart_careers/templates/area.html +++ b/sites/walmart_careers/templates/area.html @@ -1,18 +1,18 @@ {% extends "base.html" %} +{% block body_class %}l1{% endblock %} {% block title %}{{ area.name }} careers | Walmart Careers{% endblock %} {% block content %} +{% set page = content.AREA_PAGE.get(area.slug, content.AREA_PAGE_DEFAULT) %} +{% set hero_image = 'military-banner.png' if area.slug == 'Military' else area.hero_image %} +{% set roles_url = url_for('results', area=area.slug) if area.is_filterable else url_for('results') %} +{% set office_area = area.slug in ('technology', 'corporate') %} + <div class="area-hero"> - <img src="{{ url_for('static', filename='images/' ~ area.hero_image) }}" alt="{{ area.name }}"> - <div class="overlay"> - <div class="wrap"> - <h1>{{ area.name }}</h1> - <p>{{ area.blurb }}</p> - {% if area.is_filterable %} - <a class="btn btn-spark" href="{{ url_for('results', area=area.slug) }}">See all open roles</a> - {% else %} - <a class="btn btn-spark" href="{{ url_for('results') }}">See all open roles</a> - {% endif %} - </div> + <img src="{{ url_for('static', filename='images/' ~ hero_image) }}" alt="{{ area.name }}"> + <div class="area-card"> + <h1>{{ area.name }}</h1> + <p>{{ area.blurb }}</p> + <a class="btn" href="{{ roles_url }}">{{ 'See all opportunities' if area.slug == 'Military' else 'See all open roles' }}</a> </div> </div> @@ -20,13 +20,18 @@ <h1>{{ area.name }}</h1> <section> <div class="wrap"> <h2>Join our team</h2> - <div class="category-grid"> - {% for category in categories %} - <a href="{{ url_for('results', area=area.slug, category=category.slug) }}"> - <span>{{ category.name }}</span> - <span class="count">{{ counts[category.id] }} open</span> - </a> - {% endfor %} + <div class="join-grid"> + <div class="stack-card"> + <span class="stack-band band-navy"></span> + <span class="stack-band band-sky"></span> + <img src="{{ url_for('static', filename='images/' ~ page.photos[0]) }}" alt="{{ area.name }} associates"> + <div class="stack-controls" aria-hidden="true"><i>↑</i><i>‖</i><i>↓</i></div> + </div> + <ul class="category-list"> + {% for category in categories %} + <li><a href="{{ url_for('results', area=area.slug, category=category.slug) }}">{{ category.name }}</a></li> + {% endfor %} + </ul> </div> </div> </section> @@ -43,39 +48,145 @@ <h2>Programs, not a job family</h2> </section> {% endif %} -<section class="section-alt"> +<section> <div class="wrap"> <h2>Explore our benefits</h2> - <div class="benefit-list"> - {% for name, blurb, icon in content.BENEFIT_ROWS %} - <div class="benefit-row"> + <div class="benefit-tiles"> + {% for name, headline, blurb, icon in content.benefit_tiles_for('Walmart', 'salaried' if office_area else 'hourly') %} + <div class="benefit-tile tile-{{ loop.index }}"> + <img src="{{ url_for('static', filename='icons/' ~ icon) }}" alt=""> + <em>{{ name }}</em> + <b>{{ headline }}</b> + <p>{{ blurb }}</p> + </div> + {% endfor %} + </div> + <div class="benefit-grid two-col"> + {% for name, blurb, icon in content.JOB_BENEFIT_ROWS %} + <div class="benefit-item"> <img src="{{ url_for('static', filename='icons/' ~ icon) }}" alt=""> - <div><b>{{ name }}</b><span>{{ blurb }}</span></div> + <b>{{ name }}</b> + <span>{{ blurb }}</span> </div> {% endfor %} </div> + <p style="margin-top:24px"><a class="btn btn-secondary" href="{{ url_for('about_us') }}">Learn more about benefits</a></p> + </div> +</section> + +{% if office_area %} +<section class="quote-section"> + <div class="wrap"><p class="quote-band-text">{{ page.quote }}</p></div> +</section> +{% endif %} + +<section class="bento-section"> + <div class="wrap-bleed"> + <div class="bento-layout"> + <div class="bento area-bento"> + <a class="tile tile-blue tile-sq" href="{{ roles_url }}"> + <span class="tile-label">{{ page.tiles[0] }}</span><span class="circle-arrow">›</span> + </a> + <div class="tile tile-photo tile-wide"> + <img src="{{ url_for('static', filename='images/' ~ page.photos[1]) }}" alt=""> + </div> + {% if page.tiles|length > 2 %} + <div class="tile tile-photo tile-wide"> + <img src="{{ url_for('static', filename='images/' ~ page.photos[0]) }}" alt=""> + </div> + <a class="tile tile-navy tile-sq" href="{{ roles_url }}"> + <span class="tile-label">{{ page.tiles[1] }}</span><span class="circle-arrow circle-arrow-back">‹</span> + </a> + <a class="tile tile-pale tile-sq" href="{{ roles_url }}"> + <span class="tile-label">{{ page.tiles[2] }}</span><span class="circle-arrow">›</span> + </a> + {% else %} + <span class="tile-gap"></span> + <a class="tile tile-navy tile-sq" href="{{ roles_url }}"> + <span class="tile-label">{{ page.tiles[1] }}</span><span class="circle-arrow circle-arrow-back">‹</span> + </a> + {% endif %} + </div> + <div class="bento-aside"> + <h2>{{ page.headline }}</h2> + <a class="btn btn-secondary" href="{{ roles_url }}">{{ page.cta }}</a> + </div> + </div> </div> </section> +{% if not office_area %} +<section class="quote-section"> + <div class="wrap"><p class="quote-band-text">{{ page.quote }}</p></div> +</section> +{% endif %} + +{% if office_area %} <section> <div class="wrap"> - <h2>Our hubs</h2> - <div class="hub-grid"> + <h2 class="tight">{{ page.hubs_heading }}</h2> + <p class="section-blurb">{{ page.hubs_blurb }}</p> + <div class="hub-links"> {% for hub in hubs %} - {% if hub.hub_blurb %} - <div class="hub-card"> - <img src="{{ url_for('static', filename='images/' ~ hub.hub_image) }}" alt="{{ hub.hub_name }}"> - <div class="cap"> - <h3>{{ hub.hub_name }}</h3> - <p>{{ hub.hub_blurb }}</p> - <a class="btn btn-secondary btn-sm" - href="{{ url_for('results', loc=hub.city ~ ', ' ~ hub.state, radius=25) }}">See roles near {{ hub.city }}</a> - </div> - </div> + {% if hub.hub_name %} + <a href="{{ url_for('results', loc=hub.city ~ ', ' ~ hub.state, radius=25) }}">{{ hub.hub_name }} + <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"> + <path d="M4 12h15M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/> + </svg></a> {% endif %} {% endfor %} </div> - <p style="margin-top:20px"><a class="btn btn-secondary" href="{{ url_for('resources_location') }}">See all hubs</a></p> + <p style="margin-top:28px"><a class="btn btn-secondary btn-sm" href="{{ url_for('resources_location') }}">See all hubs</a></p> + </div> +</section> +{% else %} +<section> + <div class="wrap"> + <h2 class="tight">{{ content.ASSOCIATES_HEADING }}</h2> + <p class="section-blurb">{{ content.ASSOCIATES_BLURB }}</p> + <div class="video-strip dark"> + {% for name, role, _quote in page.testimonials %} + <div class="video-tile{% if loop.index == 2 %} wide{% endif %}"> + <img src="{{ url_for('static', filename='images/' ~ page.photos[loop.index0 % 2]) }}" alt=""> + <div class="v-cap"><em>Meet {{ name }}</em><b>{{ role }}</b><span class="play" aria-hidden="true"></span></div> + </div> + {% endfor %} + </div> + <div class="dots" aria-hidden="true"><i></i><i class="on"></i><i></i><i></i></div> + </div> +</section> +{% endif %} + +{% if page.testimonials %} +<section> + <div class="wrap"> + <h2>{{ content.INSPIRATION_HEADING }}</h2> + <div class="quote-cards"> + {% for name, role, quote in page.testimonials %} + <div class="quote-card{% if loop.index == 2 %} on{% endif %}"> + <b>{{ name }}, {{ role }}</b> + <p>“{{ quote }}”</p> + </div> + {% endfor %} + </div> + <div class="dots" aria-hidden="true"><i class="on"></i><i></i><i></i></div> + </div> +</section> +{% endif %} + +<section class="find-role"> + <div class="wrap"> + <h2>Find the role that's a perfect fit.</h2> + <form class="find-search" action="{{ url_for('results') }}" method="get" role="search"> + <input type="text" name="q" placeholder="{{ content.SEARCH_PLACEHOLDER }}" aria-label="Search open roles"> + <button type="submit" aria-label="Search"> + <svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true"> + <circle cx="10.5" cy="10.5" r="6" fill="none" stroke="currentColor" stroke-width="2"/> + <path d="M15.2 15.2 20 20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> + <path d="M18.4 3.2l.7 1.9 1.9.7-1.9.7-.7 1.9-.7-1.9-1.9-.7 1.9-.7z" fill="currentColor"/> + </svg> + </button> + </form> </div> </section> {% endblock %} diff --git a/sites/walmart_careers/templates/base.html b/sites/walmart_careers/templates/base.html index 0d6194c6..70c33f40 100644 --- a/sites/walmart_careers/templates/base.html +++ b/sites/walmart_careers/templates/base.html @@ -7,7 +7,7 @@ <link rel="icon" href="{{ url_for('static', filename='icons/spark-yellow.svg') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/site.css') }}"> </head> -<body> +<body class="{% block body_class %}{% endblock %}"> <a class="skip-link" href="#main">Skip to main content</a> <header class="site-header"> <div class="wrap"> @@ -42,6 +42,8 @@ <a class="nav-link" href="{{ url_for('about_us') }}">About Us</a> <a class="nav-link" href="{{ url_for('career_area', slug='Military') }}">Military</a> </nav> + {# The live home page carries no header search pill: the hero pill is the search. #} + {% if request.endpoint != 'index' %} <div class="header-search"> <form action="{{ url_for('results') }}" method="get" role="search"> <input type="text" name="q" value="{{ search_q }}" @@ -55,6 +57,9 @@ </button> </form> </div> + {% else %} + <div class="header-spacer"></div> + {% endif %} <details class="user-menu"> <summary aria-label="Account menu" title="Account menu"> {% if current_user.is_authenticated %} @@ -83,7 +88,14 @@ <hr> <a href="{{ url_for('login') }}">Login/Signup</a> {% endif %} - <span class="lang">EN</span> + <span class="lang"> + <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"> + <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.6"/> + <path d="M3 12h18M12 3c3 3.2 3 14.8 0 18M12 3c-3 3.2-3 14.8 0 18" + fill="none" stroke="currentColor" stroke-width="1.6"/> + </svg> + EN + </span> </div> </details> </div> diff --git a/sites/walmart_careers/templates/base_auth.html b/sites/walmart_careers/templates/base_auth.html new file mode 100644 index 00000000..feca2286 --- /dev/null +++ b/sites/walmart_careers/templates/base_auth.html @@ -0,0 +1,35 @@ +{# + Stripped layout for sign-in / register, mirroring identity.walmart.com: + a centred spark, no navigation, and a light link footer. +#} +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>{% block title %}Sign in | Walmart Careers{% endblock %} + + + + +
+ + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endwith %} + {% block content %}{% endblock %} +
+
+ {{ content.AUTH_COPYRIGHT }} + +
+ + diff --git a/sites/walmart_careers/templates/index.html b/sites/walmart_careers/templates/index.html index 1aed0814..3c79c4ad 100644 --- a/sites/walmart_careers/templates/index.html +++ b/sites/walmart_careers/templates/index.html @@ -1,14 +1,15 @@ {% extends "base.html" %} +{% block body_class %}l1{% endblock %} {% from "_job_card.html" import job_card %} {% block title %}Careers at Walmart{% endblock %} {% block content %}
-
+

{{ content.HERO_HEADLINE_1 }}{{ content.HERO_HEADLINE_2 }}

-
-
- -
-
- -
+

Trending roles

- {% for job in trending %}{{ job_card(job, saved_ids) }}{% endfor %} + {% for job in trending %}{{ job_card(job, saved_ids, compact=True) }}{% endfor %}
-

See all open roles

-
+
-

Grow your career here

{% for area in ribbon_areas %} - {{ area.name }} + + {{ area.name }} + + {% endfor %}
-
-
-

Guided by our values

-
-
Associates on the sales floor
-
-

People-led. Tech-powered.

-
    - {% for name, blurb in content.VALUES %} -
  • {{ name }} — {{ blurb }}
  • - {% endfor %} -
+
+
+
+
+ + Grow your career here + + +
+ An associate helping a customer in the grocery aisle +
+
+ +
+
+ A delivery drone in flight +
+ + People-led. Tech-powered. + + + + Guided by our values + + +
+ The Walmart home office campus +
+
Strive for excellence
+
+ +
+
+ +
+
Respect for the individual
+ + Sam's Club + + +
+ A Member Services associate at Sam's Club +
+
+
+

{{ content.HOME_INTRO_HEADLINE[0] }}
{{ content.HOME_INTRO_HEADLINE[1] }}

+ {{ content.HOME_INTRO_CTA }}
@@ -84,49 +110,77 @@

People-led. Tech-powered.

Explore our Benefits

-
- {% for name, blurb, icon in content.BENEFIT_ROWS %} -
- -
{{ name }}{{ blurb }}
-
- {% endfor %} +
+
+ {% for name, blurb, icon in content.BENEFIT_ROWS %} +
+ + {{ name }} + {{ blurb }} +
+ {% endfor %} +
+
+

{{ content.BENEFITS_ASIDE }}

+ {{ content.BENEFITS_CTA }} +
-

{{ content.BENEFIT_FOOTNOTE }}

-
+
-

Here, every job is a step toward something greater

-
- {% for figure, caption in content.STAT_CARDS %} -
{{ figure }}{{ caption }}
- {% endfor %} +
+

{{ content.MILESTONE_HEADING }}

+
+ {% for sentence, label, style in content.MILESTONE_BADGES %} +
+ {% if loop.index is even %}{% endif %} +
+
{% if label %}{{ label }}{% endif %}
+
{{ sentence }}
+ {% if label != '10 YEARS' %}
Walmart
{% else %}
{{ label }}
{% endif %} +
+ {% if loop.index is odd %}{% endif %} +
+ {% endfor %} +
-

See our associates in action

-
- {% for role, kicker, image in content.DAY_IN_THE_LIFE %} -
- {{ role }} -
{{ kicker }}
{{ role }}
+

{{ content.ASSOCIATES_HEADING }}

+

{{ content.ASSOCIATES_BLURB }}

+
+ {% for role, kicker, image in [('Store Coach', 'Day in the life', 'testimonial-1.jpg'), + ('Optician', 'Day in Life', 'home-carousel-1.jpg'), + ('Store Manager', 'Day in the life', 'home-milestone.jpg')] %} +
+ + +
{{ kicker }}{{ role }} +
{% endfor %}
+
-
+
-

Find the role that's a perfect fit

- - - +

{{ content.FIND_ROLE_HEADING }}

+ + +
diff --git a/sites/walmart_careers/templates/locations.html b/sites/walmart_careers/templates/locations.html index cea5bc83..ca6268b8 100644 --- a/sites/walmart_careers/templates/locations.html +++ b/sites/walmart_careers/templates/locations.html @@ -1,32 +1,56 @@ {% extends "base.html" %} +{% block body_class %}l1{% endblock %} {% block title %}Our locations | Walmart Careers{% endblock %} {% block content %} -
+
+
+ +

{{ content.LOCATIONS_HEADING }}

+ +
+
+
-

{{ content.LOCATIONS_HEADING }}

-

{{ content.LOCATIONS_BLURB }}

+

{{ content.LOCATIONS_BLURB }}

-
+

Hubs around the world

-
+
{% for hub in hubs %} -
+
{{ hub.hub_name or hub.city }} -
-

{{ hub.hub_name or hub.city }}

-

{{ hub.hub_blurb or 'A Walmart hub location.' }}

-

{{ hub.location_name }} — {{ hub.street }}, {{ hub.city }}, - {{ hub.state }} {{ hub.zip }} — {{ counts[hub.id] }} open roles

- See roles near {{ hub.city }} -
+ {{ hub.hub_name or hub.city }} +

{{ hub.hub_blurb or 'A Walmart hub location.' }}

+ + +
{% endfor %}
-

{{ content.LOCATIONS_CLOSING }}

+
+
+
+

{{ content.LOCATIONS_CLOSING }}

+
+
+
+

{{ content.FIND_ROLE_HEADING }}

+
{% endblock %} diff --git a/sites/walmart_careers/templates/login.html b/sites/walmart_careers/templates/login.html index 89306d30..a3bc1e38 100644 --- a/sites/walmart_careers/templates/login.html +++ b/sites/walmart_careers/templates/login.html @@ -1,26 +1,25 @@ -{% extends "base.html" %} +{% extends "base_auth.html" %} {% block title %}Sign in | Walmart Careers{% endblock %} {% block content %} -
-
-

Sign in to your candidate account

- {% if errors %} -
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
- {% endif %} -
- - {% if next_url %}{% endif %} -
- - -
-
- - -
- -
-

New here? Create a candidate account.

-
+
+

Sign in or create your account

+

Sign in with the email and password on your candidate account.

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ + {% if next_url %}{% endif %} +
+ + +
+
+ + +
+ +
+

New here? Create a candidate account.

{% endblock %} diff --git a/sites/walmart_careers/templates/register.html b/sites/walmart_careers/templates/register.html index a9adde46..e0b8d5c9 100644 --- a/sites/walmart_careers/templates/register.html +++ b/sites/walmart_careers/templates/register.html @@ -1,38 +1,36 @@ -{% extends "base.html" %} +{% extends "base_auth.html" %} {% block title %}Create an account | Walmart Careers{% endblock %} {% block content %} -
-
-

Create your candidate account

- {% if errors %} -
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
- {% endif %} -
- - {% if next_url %}{% endif %} -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-

Already have an account? Sign in.

-
+
+

Create your candidate account

+ {% if errors %} +
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
+ {% endif %} +
+ + {% if next_url %}{% endif %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+

Already have an account? Sign in.

{% endblock %} diff --git a/sites/walmart_careers/templates/results.html b/sites/walmart_careers/templates/results.html index b4ac9287..04bb8f42 100644 --- a/sites/walmart_careers/templates/results.html +++ b/sites/walmart_careers/templates/results.html @@ -86,7 +86,6 @@

{{ "{:,}".format(total) }} open role{{ '' if total == 1 else 's' }}

- {% if filter_count %}{{ filter_count }}{% endif %}
-

Saved roles ({{ rows|length }})

- {% if not current_user.is_authenticated %} -
-

Sign in to see your saved roles

-

Saving a role keeps it on this page so you can come back and apply later.

- Sign in - Create an account +
+
+

{{ content.SAVED_PROMO_HEADLINE[0] }}
{{ content.SAVED_PROMO_HEADLINE[1] }}

+

{{ content.SAVED_PROMO_BLURB }}

+ {% if current_user.is_authenticated %} + See all open roles + {% else %} + Sign in or create account + {% endif %}
+ A Walmart associate outside a store +
+
+
+ +
+
+

Saved roles ({{ rows|length }})

+ {% if not current_user.is_authenticated %} +

{{ content.SAVED_EMPTY_NOTE }} + Sign in to see the roles you have + saved, search for a role, or view your recommended roles below.

{% elif not rows %} -
-

You haven't saved any roles yet

-

Use the Save role button on any posting to keep it here.

- Browse open roles -
+

{{ content.SAVED_EMPTY_NOTE }} + Search for a role, or view your recommended roles below.

{% else %} -
- {% for row in rows %}{{ job_card(row.job, saved_ids) }}{% endfor %} +
+ {% for row in rows %}{{ job_card(row.job, saved_ids, unsave=True) }}{% endfor %}
{% endif %}
{# Recommendations only fill the page when there is nothing saved to show. #} {% if not current_user.is_authenticated or not rows %} -
+ From 707f67369a0f08f39e0e588a7f0db1bf1af4c893 Mon Sep 17 00:00:00 2001 From: evanz37 <89922710+evanz37@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:03:49 -0500 Subject: [PATCH 11/15] fix(walmart_careers): de-risk prior-knowledge answers, clarify tasks 15/16, fix 768px overflow Seed (instance_seed md5 b57631080969fe151e1717dbef3dd372, reproducible under PYTHONHASHSEED=0; auto-generated ids unchanged): - every pinned requisition ID copied from careers.walmart.com is now synthetic: R-2463275 -> R-2468347 (task 1), R-2451180 -> R-2456729 (task 10), CP-9046-11101 -> CP-9046-11274 (task 14), R-2413636 -> R-2417063, R-2414279 -> R-2418512, CP-1236-10888 -> CP-1236-10741 (trending) - placements may carry a qual_clause appended to both minimum-qualification options; the four postings behind tasks 1, 5 and 10 use it with non-boilerplate year counts so their texts cannot be recalled from upstream - Job.about_team column; salaried postings get per-category "What you'll bring" bullets (catalog_source.SALARIED_BRING, location slots only) Tasks: 15 asks for city Rogers + state AR only; 16 is driven through the location picker plus the Shift (Weekday Day) and Rate (Hourly) filters. Templates/CSS: header wraps below 1024px so /results and /jobs/ no longer overflow at 768px (verified 768/1024/1440 on home, results, detail); footer social glyphs inverted to white; salaried detail pages carry the reference's About the team / What you'll bring / About area / hybrid / Benefits / EEO / pay blocks ahead of the qualification sections. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01PsqvyH5ufJmpem5Vg6kayD --- sites/walmart_careers/_content.py | 62 +++++++++ sites/walmart_careers/app.py | 1 + sites/walmart_careers/catalog_source.py | 128 ++++++++++++++++-- sites/walmart_careers/seed_data.py | 17 ++- sites/walmart_careers/static/css/site.css | 16 ++- sites/walmart_careers/tasks.jsonl | 4 +- .../walmart_careers/templates/job_detail.html | 32 ++++- 7 files changed, 237 insertions(+), 23 deletions(-) diff --git a/sites/walmart_careers/_content.py b/sites/walmart_careers/_content.py index 14dadffd..b46cf365 100644 --- a/sites/walmart_careers/_content.py +++ b/sites/walmart_careers/_content.py @@ -129,6 +129,68 @@ def benefit_tiles_for(brand: str, population: str) -> list[tuple[str, str, str, "there are no preferred qualifications." ) +# Salaried detail pages: the boilerplate blocks that follow "What you'll bring" +# on the live corporate postings. Static chrome, keyed by career-area slug for +# the "About ..." paragraph; everything per-posting lives on Job. +SALARIED_ABOUT_AREA = { + "technology": ( + "About Walmart Global Tech", + "Imagine working in an environment where one line of code can make life easier for hundreds of " + "millions of people. That's what we do at Walmart Global Tech. We're a team of software engineers, " + "data scientists, cybersecurity experts and service professionals within the world's leading " + "retailer who make an epic impact and are at the forefront of the next retail disruption. People " + "are why we innovate, and people power our innovations. We are people-led and tech-empowered. We " + "train our team in the skillsets of the future and bring in experts like you to help us grow.", + ), + "corporate": ( + "About Walmart", + "Our home office and corporate teams set the direction for the world's largest retailer: the " + "strategy, the finances, the merchandise, the marketing and the people practices behind more " + "than 10,000 stores and clubs and the associates who run them. The work you do here shows up on " + "shelves and in carts within weeks, not years.", + ), + "students": ( + "About our internships", + "Our internships are paid, project-based and designed to end with a real deliverable. Interns " + "join a team, own a piece of work for the term, present it to leadership and leave with a " + "network across the business. Many of our leaders started as interns.", + ), +} +SALARIED_ABOUT_DEFAULT = ( + "About Walmart", + "Walmart Inc. is the world's largest retailer, serving more than 250 million customers every week " + "through stores, clubs and eCommerce sites in nineteen countries.", +) +SALARIED_HYBRID_NOTE = ( + "We use a hybrid way of working that is primarily in office coupled with virtual when not onsite. " + "Our campuses serve as a hub for collaboration, bring us together for purpose, and deliver on business " + "needs. This approach helps us make quicker decisions, remove location barriers across our global " + "team, and be more flexible in our personal lives." +) +SALARIED_BENEFITS_NOTE = ( + "Beyond our great compensation package, you can receive incentive awards for your performance. Other " + "great perks include 401(k) match, stock purchase plan, paid maternity and parental leave, PTO, " + "multiple health plans, and much more." +) +SALARIED_PAY_NOTE = ( + "At Walmart, we offer competitive pay as well as performance-based bonus awards and other great " + "benefits for a happier mind, body, and wallet. Health benefits include medical, vision and dental " + "coverage. Financial benefits include 401(k), stock purchase and company-paid life insurance. Paid " + "time off benefits include PTO (including sick leave), parental leave, family care leave, " + "bereavement, jury duty, and voting. Other benefits include short-term and long-term disability, " + "company discounts, Military Leave Pay, adoption and surrogacy expense reimbursement, and more." +) +SALARIED_EEO_NOTE = ( + "Walmart, Inc. is an Equal Opportunity Employer - By Choice. We believe we are best equipped to help " + "our associates, customers and the communities we serve live better when we really know them." +) +SALARIED_SCOPE_NOTE = ( + "The above information has been designed to indicate the general nature and level of work performed " + "in the role. It is not designed to contain or be interpreted as a comprehensive inventory of all " + "responsibilities and qualifications required of employees assigned to this job. The full Job " + "Description can be made available as part of the hiring process." +) + # --------------------------------------------------------------------------- # # Resources pages # --------------------------------------------------------------------------- # diff --git a/sites/walmart_careers/app.py b/sites/walmart_careers/app.py index d56b7a37..09ddae94 100644 --- a/sites/walmart_careers/app.py +++ b/sites/walmart_careers/app.py @@ -168,6 +168,7 @@ class Job(db.Model): is_trending = db.Column(db.Boolean, nullable=False, default=False) summary = db.Column(db.Text, nullable=False, default="") description = db.Column(db.Text, nullable=False, default="") + about_team = db.Column(db.Text, nullable=True) # salaried only additional_description_json = db.Column(db.Text, nullable=True) hashtag = db.Column(db.String(48), nullable=True) shift_time = db.Column(db.String(120), nullable=True) diff --git a/sites/walmart_careers/catalog_source.py b/sites/walmart_careers/catalog_source.py index 3e78e509..6cbb4134 100644 --- a/sites/walmart_careers/catalog_source.py +++ b/sites/walmart_careers/catalog_source.py @@ -283,7 +283,7 @@ "Complies with company policies, procedures, and standards of ethics and integrity.", ], "placements": [ - ("9046", "Part time", "SN", 21.35, 24.85, 2, {"job_id": "CP-9046-11101", "shift_time": "Shift may start between 6:00pm - 2:30am"}), + ("9046", "Part time", "SN", 21.35, 24.85, 2, {"job_id": "CP-9046-11274", "shift_time": "Shift may start between 6:00pm - 2:30am"}), ("9054", "Part time", "WN", 20.60, 24.10, 1, None), ("9281", "Part time", "SD,WN", 19.20, 22.70, 3, None), ("7133", "Full time", "WD", 18.80, 22.30, 4, None), @@ -604,7 +604,7 @@ ("2110", "Full time", "WD,WE", 15.50, 26.00, 3, None), ("2050", "Part time", "WE,SE", 17.50, 28.00, 2, None), ("471", "Full time", "WD,SD", 15.00, 25.00, 4, None), - ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, {"job_id": "CP-1236-10888"}), + ("1236", "Part time", "WN,SN", 16.00, 26.00, 2, {"job_id": "CP-1236-10741"}), ("5388", "Full time", "WN", 16.50, 27.00, 2, None), ("5133", "Full time", "WE,SN", 16.00, 26.50, 2, None), ], @@ -1217,9 +1217,14 @@ # placement tuple: (store_number, employment_type, min_pay, max_pay, # worker_type, qual_slots, extras) # `qual_slots` = (degree_field, option1_years, option2_years, preferred_slot) -# `extras` is an optional dict: {"job_id": ...} +# `extras` is an optional dict: {"job_id": ..., "qual_clause": ...} # Minimum-qualification text is built from the family's template with those -# slots, so every posting's Option 1 / Option 2 text is unique. +# slots, so every posting's Option 1 / Option 2 text is unique. A `qual_clause` +# is appended to both options ("..., including .") so that posting's +# qualification text is specific to this mirror. +# +# Pinned job_ids are synthetic: none of them is a requisition ID that exists on +# careers.walmart.com. # --------------------------------------------------------------------------- # SALARIED_FAMILIES = [ { @@ -1251,8 +1256,9 @@ "Guidelines (WCAG) 2.2 AA standards.", "placements": [ ("11807", "Full time", 143000, 286000, "Regular/Permanent", - ("computer science, computer engineering, computer information systems, software engineering, or related area", 4, 6, 2), - {"job_id": "R-2463275"}), + ("computer science, computer engineering, computer information systems, software engineering, or related area", 5, 7, 2), + {"job_id": "R-2468347", + "qual_clause": "including experience operating search or ML-serving systems in production"}), ("10101", "Full time", 132000, 264000, "Regular/Permanent", ("computer science, computer engineering, or related area", 5, 8, 3), None), ("12200", "Full time", 128000, 246000, "Regular/Permanent", @@ -1281,7 +1287,8 @@ "systems in production.", "placements": [ ("11003", "Full time", 110000, 220000, "Regular/Permanent", - ("computer science, computer information systems, or related area", 3, 5, 1), None), + ("computer science, computer information systems, or related area", 4, 7, 1), + {"qual_clause": "including experience building high-volume checkout or payments services"}), ("11807", "Full time", 117000, 234000, "Regular/Permanent", ("computer engineering, software engineering, or related area", 4, 7, 2), None), ("10101", "Full time", 96000, 192000, "Regular/Permanent", @@ -1311,7 +1318,7 @@ "software at scale.", "placements": [ ("12200", "Full time", 90000, 180000, "Regular/Permanent", - ("computer science or related area", 2, 4, 3), {"job_id": "R-2413636"}), + ("computer science or related area", 2, 4, 3), {"job_id": "R-2417063"}), ], }, { @@ -1334,7 +1341,7 @@ "product teams.", "placements": [ ("10101", "Full time", 110000, 220000, "Regular/Permanent", - ("business, analytics, engineering, or related area", 5, 7, 2), {"job_id": "R-2414279"}), + ("business, analytics, engineering, or related area", 5, 7, 2), {"job_id": "R-2418512"}), ("11807", "Full time", 132000, 264000, "Regular/Permanent", ("computer science, business, or related area", 6, 9, 3), None), ("11003", "Full time", 90000, 180000, "Regular/Permanent", @@ -1838,10 +1845,12 @@ "transportation operations.", "placements": [ ("10101", "Full time", 110000, 220000, "Regular/Permanent", - ("supply chain management, operations, or related area", 5, 7, 3), - {"job_id": "R-2451180"}), + ("supply chain management, operations, or related area", 6, 9, 3), + {"job_id": "R-2456729", + "qual_clause": "including experience running a last mile delivery or courier network"}), ("11003", "Full time", 117000, 234000, "Regular/Permanent", - ("industrial engineering, logistics, or related area", 3, 5, 2), None), + ("industrial engineering, logistics, or related area", 4, 6, 2), + {"qual_clause": "including experience with driver dispatch or arrival-time modeling"}), ], }, { @@ -2043,7 +2052,96 @@ # catalog edit cannot silently point this list at a different posting. # --------------------------------------------------------------------------- # TRENDING_JOB_IDS = [ - "R-2414279", - "R-2413636", - "CP-1236-10888", + "R-2418512", + "R-2417063", + "CP-1236-10741", ] + + +# --------------------------------------------------------------------------- # +# "What you'll bring" bullets for salaried postings, one template list per +# category. Slot fills are location only ({city}, {state}, {location_name}); the +# bullets deliberately never mention a degree or a number of years, which live +# solely in the Minimum Qualifications block. +# --------------------------------------------------------------------------- # +SALARIED_BRING = { + "Software Engineering and Architecture": [ + "A track record of designing, building and operating production services that hold up at retail traffic.", + "Fluency in at least one modern backend language and its ecosystem, plus comfort reading code in others.", + "Hands-on experience with distributed data stores, message queues and cloud infrastructure.", + "A test-driven approach to development and a strong commitment to code quality and documentation.", + "Clear written and spoken communication with engineers, product managers and partners across {city}.", + ], + "Product Management": [ + "Experience owning a product area end to end, from discovery through launch and iteration.", + "The ability to turn ambiguous customer problems into a crisp roadmap and measurable outcomes.", + "Comfort working daily with engineering, design and data science partners in {city}.", + "Strong written communication, including product specs and executive updates.", + ], + "Data Science and Analytics": [ + "Hands-on experience building and shipping statistical or machine learning models in production.", + "Fluency in Python or R and SQL, and comfort working with very large datasets.", + "The judgment to know when a simple model beats a complex one.", + "Experience explaining findings to non-technical partners across the {city} office.", + ], + "Information Security": [ + "Deep familiarity with threat modeling, secure design review and incident response.", + "Experience with identity, access management and cloud security controls at scale.", + "The ability to translate risk into priorities that engineering teams can act on.", + "Calm, clear communication during live incidents.", + ], + "Creative Design and UX": [ + "A portfolio that shows end-to-end design work, from research through shipped experience.", + "Fluency in modern design and prototyping tools and a working knowledge of front-end constraints.", + "Experience planning and running user research and turning it into design decisions.", + "The ability to present and defend design decisions to partners in {city}.", + ], + "Technical Program Management": [ + "Experience running large cross-functional programs with many engineering teams.", + "Enough technical depth to challenge estimates and spot dependencies early.", + "A bias for clear plans, visible risks and honest status.", + "Strong facilitation skills across the {city} office and remote partners.", + ], + "Information Technology": [ + "Experience supporting enterprise endpoints, identity systems and collaboration tools.", + "Scripting skills for automating repetitive support and provisioning tasks.", + "A customer-first approach to troubleshooting and a habit of documenting fixes.", + "Comfort supporting associates on site in {city} and remotely.", + ], + "Accounting and Finance": [ + "Experience owning forecasts, budgets or close processes for a large business unit.", + "Advanced spreadsheet and financial modeling skills, plus comfort with planning systems.", + "The ability to explain variances to operators and executives in plain language.", + "Attention to detail and a strong sense of ownership over the numbers.", + ], + "Human Resources": [ + "Experience partnering with leaders on talent, organization design and associate relations.", + "Working knowledge of employment practices and the judgment to apply them fairly.", + "Strong coaching and facilitation skills.", + "Comfort supporting teams across the {city} office and the field.", + ], + "Marketing and Advertising": [ + "Experience planning and running integrated campaigns across digital and in-store channels.", + "Fluency in campaign measurement and the ability to act on what the data says.", + "Strong creative judgment and clear briefing skills for agency and in-house partners.", + "Comfort presenting plans and results to senior leaders in {city}.", + ], + "Merchandising": [ + "Experience owning assortment, pricing or replenishment decisions for a category.", + "Strong analytical skills and comfort working in large planning and forecasting systems.", + "The ability to negotiate with suppliers and build long-term partnerships.", + "A customer-first mindset and a habit of walking the stores.", + ], + "Business Operations": [ + "Experience owning operational metrics and the processes behind them.", + "Strong analytical skills, including the ability to build and interpret operational dashboards.", + "Comfort working across product, data science and field operations partners.", + "A habit of spending time where the work happens, not only in the model.", + ], + "Internship": [ + "Current enrollment in a degree program with an expected graduation date after the internship term.", + "Curiosity about how a large retailer runs and a willingness to ask questions.", + "Comfort working in a team and presenting your project to leaders in {city}.", + "Availability for the full internship term.", + ], +} diff --git a/sites/walmart_careers/seed_data.py b/sites/walmart_careers/seed_data.py index 319e6122..73ae0fab 100644 --- a/sites/walmart_careers/seed_data.py +++ b/sites/walmart_careers/seed_data.py @@ -295,7 +295,15 @@ def _build_jobs(areas, categories, stores) -> list[Job]: "yp": yp, } paragraphs = [p.format(**fmt) for p in family["do"]] - paragraphs.append("About Team: " + family["about_team"].format(**fmt)) + clause = extras.get("qual_clause") + + def qualification(template: str) -> str: + text = template.format(**fmt) + if clause: + text = text.rstrip(".") + ", " + clause + "." + return text + + bring = [b.format(**fmt) for b in source.SALARIED_BRING[family["category"]]] job = Job( job_id=job_id, population="salaried", @@ -314,7 +322,8 @@ def _build_jobs(areas, categories, stores) -> list[Job]: is_trending=job_id in trending, summary=family["summary"].format(**fmt), description="\n\n".join(paragraphs), - additional_description_json=None, + about_team=family["about_team"].format(**fmt), + additional_description_json=dumps_json(bring), hashtag=None, shift_time=None, positions_available=None, @@ -323,8 +332,8 @@ def _build_jobs(areas, categories, stores) -> list[Job]: job_posting_id=f"JOB_POSTING-3-{posting_seq}", min_qualifications_json=dumps_json( [ - family["min_qual_option1"].format(**fmt), - family["min_qual_option2"].format(**fmt), + qualification(family["min_qual_option1"]), + qualification(family["min_qual_option2"]), ] ), preferred_qualifications=family["preferred"].format(**fmt), diff --git a/sites/walmart_careers/static/css/site.css b/sites/walmart_careers/static/css/site.css index 42ced8fb..10d77a1f 100644 --- a/sites/walmart_careers/static/css/site.css +++ b/sites/walmart_careers/static/css/site.css @@ -566,6 +566,7 @@ a.tile.tile-sky:hover, a.tile.tile-pale:hover { color: var(--ld-blue-160); } .detail-body ul { margin: 0 0 16px; padding-left: 20px; } .detail-body li { margin-bottom: 8px; } .hashtag { font-weight: 400; color: var(--ld-blue-100); } +.detail-body .sub-heading { font-weight: 700; margin: 22px 0 6px; } .legal { font-size: 13px; color: var(--ld-text-subtle); margin-top: 20px; } .benefit-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } @@ -720,10 +721,23 @@ table.data th { font-size: 14px; text-transform: uppercase; letter-spacing: .04e .footer-cols ul { list-style: none; margin: 0; padding: 0; } .footer-cols li { margin-bottom: 9px; font-size: 14px; } .social { display: flex; gap: 16px; margin-bottom: 28px; } -.social img { width: 26px; height: 26px; } +/* the harvested glyphs are dark (#151F29); the footer is navy, so invert them */ +.social img { width: 26px; height: 26px; filter: brightness(0) invert(1); } .legal-text { font-size: 12px; color: #cfd9ea; margin-bottom: 16px; } .footer-bottom { display: flex; gap: 22px; flex-wrap: wrap; font-size: 12px; padding-top: 10px; } +/* Header: below 1024px the five nav items plus the search pill no longer fit on one + row (the header used to force the viewport wider than 768px), so the header wraps + and the search pill takes a full second row. */ +@media (max-width: 1024px) { + .site-header .wrap { flex-wrap: wrap; gap: 8px 12px; padding: 8px 16px; } + .main-nav { flex: 1 1 auto; gap: 2px; min-width: 0; overflow-x: auto; } + .main-nav .nav-link, .nav-menu > summary { font-size: 14px; padding: 8px 8px; } + .header-search { flex: 1 0 100%; order: 10; } + .header-search form { max-width: none; } + .header-spacer { display: none; } +} + @media (max-width: 1200px) { :root { --gutter: 32px; } .results-layout, .detail-layout { grid-template-columns: 1fr; } diff --git a/sites/walmart_careers/tasks.jsonl b/sites/walmart_careers/tasks.jsonl index 11b80493..cbb7cf91 100644 --- a/sites/walmart_careers/tasks.jsonl +++ b/sites/walmart_careers/tasks.jsonl @@ -13,8 +13,8 @@ {"web_name": "Walmart Careers", "id": "Walmart Careers--12", "ques": "Log in as bob.c@test.com (password: TestPass123!), open your Saved roles, and remove the saved role that is at a Neighborhood Market store.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--13", "ques": "Log in as carol.d@test.com (password: TestPass123!), apply to the Pharmacy Technician posting in Tacoma, WA using phone number 253-555-0142, and report the confirmation number shown after submitting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/contact-details?from=%2Fus%2Fen%2Fjobs%2Fapply&lang=en&country=us"} {"web_name": "Walmart Careers", "id": "Walmart Careers--14", "ques": "Register a new account with an email and password of your choice, then save the eCom Warehouse Worker posting at eComm Whse Logistics #9046 in Marcy, NY to your Saved roles.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/jobs/CP-9046-11101"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the phone number is 479-555-0199 and the city is Rogers, AR, then open My applications and report the confirmation number of your existing application.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} -{"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Find every hourly Cashier posting in Puerto Rico that lists Weekday Day among its shifts. Report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--15", "ques": "Log in as david.k@test.com (password: TestPass123!) and update your account so the city is Rogers and the state is AR, then open My applications and report the confirmation number of your existing application.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home"} +{"web_name": "Walmart Careers", "id": "Walmart Careers--16", "ques": "Set the location to Puerto Rico, then use the Shift filter for Weekday Day and the Rate filter for Hourly. Among the Cashier postings, report the requisition ID of the one with the most open positions and that number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/results?searchQuery=cashier"} {"web_name": "Walmart Careers", "id": "Walmart Careers--17", "ques": "Log in as alice.j@test.com (password: TestPass123!). Exactly one of your saved roles is a Part time job; apply to it with your account email, then report that posting's shift start window and your confirmation number.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/candidate-home/saved-roles"} {"web_name": "Walmart Careers", "id": "Walmart Careers--18", "ques": "Open the Supply Chain and Transportation career area page, go to its Drivers category, and compare the Class A CDL Truck Driver postings at the Ottawa, KS and Williamsburg, VA facilities. For whichever of the two lists more open positions, report its requisition ID, its shift start window and that number of open positions.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/supply-chain-and-transportation"} {"web_name": "Walmart Careers", "id": "Walmart Careers--19", "ques": "Log in as bob.c@test.com (password: TestPass123!). From the Stores and Clubs career area page, open the Digital Pickup and Delivery category, filter it to Full time roles, and open the posting with the fewest open positions. Save that role to your Saved roles, then report its requisition ID and the street address shown on the posting.", "web": "http://localhost:40019/", "upstream_url": "https://careers.walmart.com/us/en/home/careers-areas/stores-and-clubs"} diff --git a/sites/walmart_careers/templates/job_detail.html b/sites/walmart_careers/templates/job_detail.html index e5dad1f5..590d95af 100644 --- a/sites/walmart_careers/templates/job_detail.html +++ b/sites/walmart_careers/templates/job_detail.html @@ -78,7 +78,12 @@
-
+
+ {{ life_block(content.LIFE_AT_WALMART_HEADING, content.LIFE_AT_WALMART_FIELD) }} +
+ +
-

{{ content.LIFE_AT_WALMART_HEADING }}

-
-
- {% for paragraph in content.LIFE_AT_WALMART %}

{{ paragraph }}

{% endfor %} -
    - {% for title, blurb in content.VALUES %}
  • {{ title }} — {{ blurb }}
  • {% endfor %} -
-
-
- Walmart associates -
+

Guided by our values

+
+ {% for title, blurb in content.VALUES %} +

{{ title }}

{{ blurb }}

+ {% endfor %}
@@ -42,7 +42,14 @@

{{ content.LIFE_AT_WALMART_HEADING }}

Explore our career areas

- {% for area in areas %}{{ area.name }}{% endfor %} + {% for area in areas %} + + {{ area.name }} + + + {% endfor %}
diff --git a/sites/walmart_careers/templates/apply_confirm.html b/sites/walmart_careers/templates/apply_confirm.html index 397ca2d6..3da18b9b 100644 --- a/sites/walmart_careers/templates/apply_confirm.html +++ b/sites/walmart_careers/templates/apply_confirm.html @@ -1,10 +1,11 @@ {% extends "base.html" %} {% block title %}Review your application | Walmart Careers{% endblock %} {% block content %} -
-
+
+
+

Review your application

-

Step 2 of 2 — check your details, then submit.

+

Step 2 of 2 — check your details, then submit.

  • Role{{ job.title }}
  • Location{{ job.store.banner }} #{{ job.store.store_number }}, {{ job.store.city }}, {{ job.store.state }}
  • @@ -15,8 +16,10 @@

    Review your application

- - Edit details +
+ Edit details + +
diff --git a/sites/walmart_careers/templates/apply_contact.html b/sites/walmart_careers/templates/apply_contact.html index d293a45b..7361839b 100644 --- a/sites/walmart_careers/templates/apply_contact.html +++ b/sites/walmart_careers/templates/apply_contact.html @@ -1,10 +1,11 @@ {% extends "base.html" %} {% block title %}Apply to {{ job.title }} | Walmart Careers{% endblock %} {% block content %} -
-
-

Apply: {{ job.title }}

-

{{ job.store.banner }} #{{ job.store.store_number }} — +

+
+
+

{{ content.APPLY_HEADING }}

+

Apply: {{ job.title }} — {{ job.store.banner }} #{{ job.store.store_number }}, {{ job.store.city }}, {{ job.store.state }} — Step 1 of 2: contact details

{% if errors %}
    {% for error in errors %}
  • {{ error }}
  • {% endfor %}
@@ -14,14 +15,17 @@

Apply: {{ job.title }}

+

{{ content.APPLY_EMAIL_HINT }}

-
- - -
-
- - +
+
+ + +
+
+ + +
@@ -30,11 +34,15 @@

Apply: {{ job.title }}

- - Back to the role +
+ Back to the role + +
diff --git a/sites/walmart_careers/templates/apply_submitted.html b/sites/walmart_careers/templates/apply_submitted.html index ed755596..add9d830 100644 --- a/sites/walmart_careers/templates/apply_submitted.html +++ b/sites/walmart_careers/templates/apply_submitted.html @@ -1,8 +1,9 @@ {% extends "base.html" %} {% block title %}Application submitted | Walmart Careers{% endblock %} {% block content %} -
-
+
+
+

Application submitted

Thanks, {{ application.first_name }}. Your application for {{ job.title }} at {{ job.store.banner }} #{{ job.store.store_number }} in {{ job.store.city }}, {{ job.store.state }} @@ -13,10 +14,12 @@

Application submitted

  • Email{{ application.email }}
  • Phone{{ application.phone }}
  • - Keep browsing roles - {% if current_user.is_authenticated %} - My applications - {% endif %} +
    + Keep browsing roles + {% if current_user.is_authenticated %} + My applications + {% endif %} +
    {% endblock %} diff --git a/sites/walmart_careers/templates/area.html b/sites/walmart_careers/templates/area.html index e3451c03..21a9239e 100644 --- a/sites/walmart_careers/templates/area.html +++ b/sites/walmart_careers/templates/area.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "_benefits.html" import benefits_block %} {% block body_class %}l1{% endblock %} {% block title %}{{ area.name }} careers | Walmart Careers{% endblock %} {% block content %} @@ -6,13 +7,14 @@ {% set hero_image = 'military-banner.png' if area.slug == 'Military' else area.hero_image %} {% set roles_url = url_for('results', area=area.slug) if area.is_filterable else url_for('results') %} {% set office_area = area.slug in ('technology', 'corporate') %} +{% set military = area.slug == 'Military' %} @@ -35,6 +37,35 @@

    Join our team

    +{% elif military %} +{# The live Military page opens with two feature rows and three program tiles. #} +
    +
    + {% for heading, blurb, cta, photo in content.MILITARY_FEATURES %} +
    + +
    +

    {{ heading }}

    +

    {{ blurb }}

    + {{ cta }} +
    +
    + {% endfor %} +
    + {% for heading, blurb, photo in content.MILITARY_PROGRAMS %} + {% set target = url_for('register') if loop.last else (url_for('results', area='students', type='Intern') if loop.index == 2 else roles_url) %} + + + {{ heading }} +

    {{ blurb }}

    + +
    + {% endfor %} +
    +
    +
    {% else %}
    @@ -48,31 +79,16 @@

    Programs, not a job family

    {% endif %} -
    -
    -

    Explore our benefits

    -
    - {% for name, headline, blurb, icon in content.benefit_tiles_for('Walmart', 'salaried' if office_area else 'hourly') %} -
    - - {{ name }} - {{ headline }} -

    {{ blurb }}

    -
    - {% endfor %} -
    -
    - {% for name, blurb, icon in content.JOB_BENEFIT_ROWS %} -
    - - {{ name }} - {{ blurb }} -
    - {% endfor %} -
    -

    Learn more about benefits

    -
    -
    +{% set benefits_html %} +
    + {{ benefits_block('Explore our benefits', + content.benefit_tiles_for('Walmart', 'salaried' if office_area else 'hourly'), + content.JOB_BENEFIT_ROWS, 'Learn more about benefits', url_for('about_us')) }} +
    +{% endset %} + +{# Field areas show the benefits right after "Join our team"; Military after its stories strip. #} +{% if not military %}{{ benefits_html }}{% endif %} {% if office_area %}
    @@ -115,6 +131,24 @@

    {{ page.headline }}

    +{% if area.slug == 'stores-and-clubs' %} +{% set meet = content.MEET_STORE_COACH %} +
    +
    +
    +
    + +
    {{ meet.kicker }}{{ meet.role }}
    +
    +
    +

    Meet {{ meet.name }}

    +

    {{ meet.blurb }}

    +
    +
    +
    +
    +{% endif %} + {% if not office_area %}

    {{ page.quote }}

    @@ -147,7 +181,7 @@

    {{ content.ASSOCIATES_HEADING }}

    {% for name, role, _quote in page.testimonials %}
    - +
    Meet {{ name }}{{ role }}
    {% endfor %} @@ -157,6 +191,8 @@

    {{ content.ASSOCIATES_HEADING }}

    {% endif %} +{% if military %}{{ benefits_html }}{% endif %} + {% if page.testimonials %}
    diff --git a/sites/walmart_careers/templates/base.html b/sites/walmart_careers/templates/base.html index 70c33f40..69094570 100644 --- a/sites/walmart_careers/templates/base.html +++ b/sites/walmart_careers/templates/base.html @@ -151,12 +151,16 @@

    About

    + {# The live footer draws each harvested (#151F29) glyph on a 44px white disc. #} - + {% set rights = 'applicant rights under Federal Employment Laws.' %} + + +

    Explore something new

    @@ -18,24 +22,48 @@

    Explore something new

    + +{% for heading, questions in content.HIRING_FAQ %} + {% set intro = content.HIRING_FAQ_INTROS[loop.index0] %} +
    +
    +
    +
    + +

    {{ heading }}

    +

    {{ intro[0] }}

    +
    +
    + {% for question, answer in questions %} +
    + {{ question }} +

    {{ answer }}

    +
    + {% endfor %} +
    +
    +
    +
    +{% endfor %} +
    -
    - {% for heading, questions in content.HIRING_FAQ %} -

    {{ heading }}

    - {% for question, answer in questions %} -
    - {{ question }} -

    {{ answer }}

    -
    - {% endfor %} - {% endfor %} +
    +
    + +
    +

    {{ content.HIRING_SIMULATOR.heading }}

    +

    {{ content.HIRING_SIMULATOR.blurb }}

    + {{ content.HIRING_SIMULATOR.cta }} +
    +
    -
    + + diff --git a/sites/walmart_careers/templates/index.html b/sites/walmart_careers/templates/index.html index 3c79c4ad..27442668 100644 --- a/sites/walmart_careers/templates/index.html +++ b/sites/walmart_careers/templates/index.html @@ -63,7 +63,7 @@

    Trending roles

    An associate helping a customer in the grocery aisle
    - +
    A delivery drone in flight @@ -154,11 +154,10 @@

    {{ content.MILESTONE_HEADING }}

    {{ content.ASSOCIATES_HEADING }}

    {{ content.ASSOCIATES_BLURB }}

    - {% for role, kicker, image in [('Store Coach', 'Day in the life', 'testimonial-1.jpg'), - ('Optician', 'Day in Life', 'home-carousel-1.jpg'), - ('Store Manager', 'Day in the life', 'home-milestone.jpg')] %} + {% for role, kicker in [('Store Coach', 'Day in the life'), + ('Optician', 'Day in Life'), + ('Store Manager', 'Day in the life')] %}
    -
    {{ kicker }}{{ role }}
    diff --git a/sites/walmart_careers/templates/job_detail.html b/sites/walmart_careers/templates/job_detail.html index 590d95af..c91e416a 100644 --- a/sites/walmart_careers/templates/job_detail.html +++ b/sites/walmart_careers/templates/job_detail.html @@ -1,5 +1,7 @@ {% extends "base.html" %} {% from "_job_card.html" import job_card %} +{% from "_benefits.html" import benefits_block %} +{% from "_life.html" import life_block %} {% block title %}{{ job.title }} in {{ job.store.city }}, {{ job.store.state }} | Walmart Careers{% endblock %} {% block content %} {% set salaried = job.population == 'salaried' %} @@ -73,27 +75,27 @@
    -
    -
    - +{# + Below the masthead the live page is one two-column layout: the sticky + "On this page" panel on the left, and on the right the role details, the + benefits block and "Life at Walmart" one after another in the same column. +#} +
    + -
    +
    +

    {{ job.title }}

    @@ -108,14 +110,14 @@

    {{ job.title }}

    {% if job.positions_available %}
    {{ job.positions_available }} open position{{ '' if job.positions_available == 1 else 's' }}
    {% endif %} -
    {{ job.job_id }}
    + {{ job.job_id }}
    {{ map_svg|safe }}
    - {% if salaried %} - -

    {{ job.title }}

    {% else %} - -

    {{ job.title }}

    * Must be at least 18 years old

    {% endif %} -
    +
    {% if salaried %}

    Position Summary...

    {{ job.summary }}

    @@ -193,12 +195,12 @@

    What you'll do...

    The annual salary range for this position is {{ job.pay_range }}. Additional compensation includes annual or quarterly performance bonuses.

    Minimum Qualifications...

    -

    {{ content.MIN_QUAL_PREAMBLE }}

    +

    {{ content.MIN_QUAL_PREAMBLE }}

      {% for option in job.min_qualifications %}
    • {{ option }}
    • {% endfor %}

    Preferred Qualifications...

    -

    {{ content.PREF_QUAL_PREAMBLE }}

    +

    {{ content.PREF_QUAL_PREAMBLE }}

    {{ job.preferred_qualifications }}

    Primary Location...

    {{ job.store.street }}, {{ job.store.city }}, {{ job.store.state }} {{ job.store.zip }}, @@ -212,7 +214,9 @@

    What you'll bring

      {% for bullet in job.additional_description %}
    • {{ bullet }}
    • {% endfor %}
    - {% if job.hashtag %}

    {{ job.hashtag }}

    {% endif %} + {% if job.hashtag %} +

    {{ job.hashtag }}

    + {% endif %} @@ -220,53 +224,21 @@

    What you'll bring

    -
    -
    -
    -
    -

    Benefits you'll enjoy

    -
    - {% for title, kicker, body, icon in benefit_tiles %} -
    - - {{ title }} - {{ kicker }} -

    {{ body }}

    -
    - {% endfor %} -
    -
    - {% for name, blurb, icon in content.JOB_BENEFIT_ROWS %} -
    - -
    {{ name }}{{ blurb }}
    -
    - {% endfor %} -
    -

    Learn more

    -
    -
    + {{ benefits_block("Benefits you'll enjoy", benefit_tiles, content.JOB_BENEFIT_ROWS, + 'Learn more', url_for('resources_hiring'), section_id='benefits') }} -
    -
    -

    {{ content.LIFE_AT_WALMART_HEADING }}

    -
    -
    - {% for paragraph in content.LIFE_AT_WALMART %}

    {{ paragraph }}

    {% endfor %} -
    -
    Walmart associates
    -
    -
    {{ content.LIFE_AT_WALMART_QUOTE }}
    + {{ life_block(content.LIFE_AT_WALMART_HEADING, + content.LIFE_AT_WALMART_CORP if salaried else content.LIFE_AT_WALMART_FIELD) }}
    -
    +
    {% if related %} -
    -
    +