From 912bb2ab6a4e1ba4b52f06f28ee8c2fc4a2c74bf Mon Sep 17 00:00:00 2001 From: StephenQSstarThomas Date: Wed, 15 Jul 2026 05:04:34 -0400 Subject: [PATCH 1/3] Add Ohio State University mirror site (17th site, port 40016) Integrate the osu mirror from PR #12 onto the current main as an additive 17th site instead of the PR's stale base (which replaced merriam_webster and reused port 40015). Registered in all three places (websyn_start.sh, control_server.py, Dockerfile EXPOSE 40000-40016); the readiness gate in websyn_start.sh is now derived from ${#SITES[@]} so it no longer hardcodes the site count. osu ships no frozen DB/images in git or HF: its data is generated from seed_data.py, so a Dockerfile build step creates instance_seed/osu.db so the boot reset and /reset/osu have a seed to restore from. Verified: image builds, osu serves on 40016, and /reset byte-identity holds. --- Dockerfile | 15 +- control_server.py | 2 +- sites/osu/_health.py | 17 + sites/osu/app.py | 818 +++++++++++++++ sites/osu/seed_data.py | 1038 ++++++++++++++++++++ sites/osu/static/css/.gitkeep | 0 sites/osu/static/js/.gitkeep | 0 sites/osu/tasks.jsonl | 20 + sites/osu/templates/404.html | 15 + sites/osu/templates/500.html | 12 + sites/osu/templates/about.html | 132 +++ sites/osu/templates/academics.html | 57 ++ sites/osu/templates/account.html | 74 ++ sites/osu/templates/admissions.html | 108 ++ sites/osu/templates/athletics.html | 89 ++ sites/osu/templates/athletics_team.html | 81 ++ sites/osu/templates/base.html | 259 +++++ sites/osu/templates/department_detail.html | 74 ++ sites/osu/templates/departments.html | 36 + sites/osu/templates/event_detail.html | 87 ++ sites/osu/templates/events.html | 105 ++ sites/osu/templates/faculty.html | 82 ++ sites/osu/templates/faculty_profile.html | 94 ++ sites/osu/templates/index.html | 129 +++ sites/osu/templates/login.html | 45 + sites/osu/templates/news.html | 96 ++ sites/osu/templates/news_article.html | 72 ++ sites/osu/templates/program_detail.html | 90 ++ sites/osu/templates/programs.html | 98 ++ sites/osu/templates/register.html | 65 ++ sites/osu/templates/research.html | 64 ++ sites/osu/templates/research_center.html | 72 ++ sites/osu/templates/search.html | 141 +++ websyn_start.sh | 12 +- 34 files changed, 4090 insertions(+), 9 deletions(-) create mode 100644 sites/osu/_health.py create mode 100644 sites/osu/app.py create mode 100644 sites/osu/seed_data.py create mode 100644 sites/osu/static/css/.gitkeep create mode 100644 sites/osu/static/js/.gitkeep create mode 100644 sites/osu/tasks.jsonl create mode 100644 sites/osu/templates/404.html create mode 100644 sites/osu/templates/500.html create mode 100644 sites/osu/templates/about.html create mode 100644 sites/osu/templates/academics.html create mode 100644 sites/osu/templates/account.html create mode 100644 sites/osu/templates/admissions.html create mode 100644 sites/osu/templates/athletics.html create mode 100644 sites/osu/templates/athletics_team.html create mode 100644 sites/osu/templates/base.html create mode 100644 sites/osu/templates/department_detail.html create mode 100644 sites/osu/templates/departments.html create mode 100644 sites/osu/templates/event_detail.html create mode 100644 sites/osu/templates/events.html create mode 100644 sites/osu/templates/faculty.html create mode 100644 sites/osu/templates/faculty_profile.html create mode 100644 sites/osu/templates/index.html create mode 100644 sites/osu/templates/login.html create mode 100644 sites/osu/templates/news.html create mode 100644 sites/osu/templates/news_article.html create mode 100644 sites/osu/templates/program_detail.html create mode 100644 sites/osu/templates/programs.html create mode 100644 sites/osu/templates/register.html create mode 100644 sites/osu/templates/research.html create mode 100644 sites/osu/templates/research_center.html create mode 100644 sites/osu/templates/search.html diff --git a/Dockerfile b/Dockerfile index 1e86b1d0..902045ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # WebHarbor — slim, self-contained image. -# 16 Flask mirror sites + control plane on :8101. +# 17 Flask mirror sites + control plane on :8101. FROM python:3.12-slim-bookworm @@ -33,6 +33,17 @@ 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-40015 +# osu ships no frozen DB in git/HF (its data is generated deterministically from +# seed_data.py). Build its instance_seed/osu.db at image-build time so the boot +# reset (`cp -a instance_seed instance` in websyn_start.sh) and /reset/osu both +# have a seed to restore from. +RUN cd /opt/WebSyn/osu && python3 -c "\ +import app; \ +import os, shutil; \ +os.makedirs('instance_seed', exist_ok=True); \ +shutil.copy2('instance/osu.db', 'instance_seed/osu.db'); \ +print('osu seed DB generated at build time.')" && rm -rf /opt/WebSyn/osu/instance + +EXPOSE 8101 40000-40016 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index 4b6b995e..b0c1dc02 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', + 'coursera', 'espn', 'merriam_webster', 'osu', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/osu/_health.py b/sites/osu/_health.py new file mode 100644 index 00000000..0d868f36 --- /dev/null +++ b/sites/osu/_health.py @@ -0,0 +1,17 @@ +"""Health check module for OSU mirror site.""" + + +def health_check(app, db, College, Program): + """Return health status dict.""" + try: + with app.app_context(): + college_count = College.query.count() + program_count = Program.query.count() + return { + 'ok': True, + 'site': 'osu', + 'colleges': college_count, + 'programs': program_count, + } + except Exception as e: + return {'ok': False, 'error': str(e)} diff --git a/sites/osu/app.py b/sites/osu/app.py new file mode 100644 index 00000000..86a93217 --- /dev/null +++ b/sites/osu/app.py @@ -0,0 +1,818 @@ +#!/usr/bin/env python3 +"""Ohio State University mirror — Flask application.""" +import os +import re +from datetime import datetime +from math import ceil + +from flask import (Flask, render_template, request, redirect, url_for, + flash, jsonify, session, abort, g) +from flask_sqlalchemy import SQLAlchemy +from flask_login import (LoginManager, UserMixin, login_user, logout_user, + login_required, current_user) +from flask_wtf import FlaskForm +from flask_wtf.csrf import CSRFProtect +from flask_bcrypt import Bcrypt +from wtforms import StringField, PasswordField, TextAreaField, SelectField +from wtforms.validators import DataRequired, Email, Length, EqualTo, Optional + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'osu-mirror-secret-key-2024' +app.config['SQLALCHEMY_DATABASE_URI'] = ( + f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'osu.db')}") +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['WTF_CSRF_TIME_LIMIT'] = None + +os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True) + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message = 'Please sign in to continue.' +login_manager.login_message_category = 'info' +csrf = CSRFProtect(app) + +PER_PAGE = 20 + +# ─── Helpers ────────────────────────────────────────────────────────────────── + +def slugify(text): + if not text: + return '' + s = re.sub(r'[^a-zA-Z0-9\s-]', '', text) + s = re.sub(r'[\s]+', '-', s.strip().lower()) + return s + +# ─── Models ─────────────────────────────────────────────────────────────────── + +class User(db.Model, UserMixin): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False, index=True) + username = db.Column(db.String(80), unique=True, nullable=False, index=True) + password_hash = db.Column(db.String(255), nullable=False) + full_name = db.Column(db.String(150), nullable=False, default='') + role = db.Column(db.String(30), default='student') + bio = db.Column(db.Text, default='') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + bookmarks = db.relationship('Bookmark', backref='user', lazy=True, + cascade='all, delete-orphan') + + def set_password(self, pw): + self.password_hash = bcrypt.generate_password_hash(pw).decode('utf-8') + + def check_password(self, pw): + return bcrypt.check_password_hash(self.password_hash, pw) + + +class College(db.Model): + __tablename__ = 'colleges' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(200), nullable=False) + slug = db.Column(db.String(200), unique=True, nullable=False, index=True) + description = db.Column(db.Text, default='') + dean = db.Column(db.String(150), default='') + founded_year = db.Column(db.Integer, default=1870) + undergrad_count = db.Column(db.Integer, default=1000) + grad_count = db.Column(db.Integer, default=500) + campus = db.Column(db.String(100), default='Columbus') + + departments = db.relationship('Department', backref='college', lazy=True) + programs = db.relationship('Program', backref='college', lazy=True) + research_centers = db.relationship('ResearchCenter', backref='college', lazy=True) + + +class Department(db.Model): + __tablename__ = 'departments' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(200), nullable=False) + slug = db.Column(db.String(200), unique=True, nullable=False, index=True) + college_id = db.Column(db.Integer, db.ForeignKey('colleges.id'), nullable=False) + description = db.Column(db.Text, default='') + chair = db.Column(db.String(150), default='') + phone = db.Column(db.String(30), default='') + location = db.Column(db.String(200), default='') + + faculty = db.relationship('Faculty', backref='department', lazy=True) + programs = db.relationship('Program', backref='department', lazy=True) + + +class Program(db.Model): + __tablename__ = 'programs' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(300), nullable=False) + slug = db.Column(db.String(300), unique=True, nullable=False, index=True) + degree_type = db.Column(db.String(20), default='BA') + college_id = db.Column(db.Integer, db.ForeignKey('colleges.id'), nullable=True) + department_id = db.Column(db.Integer, db.ForeignKey('departments.id'), nullable=True) + description = db.Column(db.Text, default='') + requirements = db.Column(db.Text, default='') + units = db.Column(db.Integer, default=120) + duration_years = db.Column(db.Float, default=4.0) + application_deadline = db.Column(db.String(80), default='') + is_online = db.Column(db.Boolean, default=False) + gre_required = db.Column(db.Boolean, default=False) + + +class NewsArticle(db.Model): + __tablename__ = 'news_articles' + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(300), nullable=False) + slug = db.Column(db.String(300), unique=True, nullable=False, index=True) + category = db.Column(db.String(50), default='Campus Life') + author = db.Column(db.String(150), default='OSU News Staff') + published_date = db.Column(db.DateTime, default=datetime.utcnow) + content = db.Column(db.Text, default='') + summary = db.Column(db.Text, default='') + tags = db.Column(db.String(500), default='') + view_count = db.Column(db.Integer, default=0) + featured = db.Column(db.Boolean, default=False) + + +class Event(db.Model): + __tablename__ = 'events' + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(300), nullable=False) + description = db.Column(db.Text, default='') + start_datetime = db.Column(db.DateTime, nullable=False) + end_datetime = db.Column(db.DateTime, nullable=True) + location = db.Column(db.String(300), default='') + building = db.Column(db.String(200), default='') + campus = db.Column(db.String(100), default='Columbus') + category = db.Column(db.String(50), default='Lecture') + organizer = db.Column(db.String(200), default='') + registration_required = db.Column(db.Boolean, default=False) + cost = db.Column(db.String(50), default='Free') + + +class ResearchCenter(db.Model): + __tablename__ = 'research_centers' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(300), nullable=False) + slug = db.Column(db.String(300), unique=True, nullable=False, index=True) + description = db.Column(db.Text, default='') + director = db.Column(db.String(150), default='') + college_id = db.Column(db.Integer, db.ForeignKey('colleges.id'), nullable=True) + focus_areas = db.Column(db.String(500), default='') + url = db.Column(db.String(300), default='') + founded_year = db.Column(db.Integer, default=2000) + + +class Faculty(db.Model): + __tablename__ = 'faculty' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(150), nullable=False) + slug = db.Column(db.String(200), unique=True, nullable=False, index=True) + title = db.Column(db.String(200), default='Professor') + department_id = db.Column(db.Integer, db.ForeignKey('departments.id'), nullable=True) + email = db.Column(db.String(120), default='') + office = db.Column(db.String(200), default='') + phone = db.Column(db.String(30), default='') + research_interests = db.Column(db.String(500), default='') + bio = db.Column(db.Text, default='') + is_emeritus = db.Column(db.Boolean, default=False) + + +class AthleticTeam(db.Model): + __tablename__ = 'athletic_teams' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(200), nullable=False) + slug = db.Column(db.String(200), unique=True, nullable=False, index=True) + sport = db.Column(db.String(100), nullable=False) + gender = db.Column(db.String(20), default='Men') + conference = db.Column(db.String(100), default='Big Ten') + coach = db.Column(db.String(150), default='') + home_venue = db.Column(db.String(200), default='') + national_titles = db.Column(db.Integer, default=0) + recent_record = db.Column(db.String(50), default='') + + +class Bookmark(db.Model): + __tablename__ = 'bookmarks' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + item_type = db.Column(db.String(50), nullable=False) + item_id = db.Column(db.Integer, nullable=False) + note = db.Column(db.Text, default='') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +# ─── Forms ──────────────────────────────────────────────────────────────────── + +class LoginForm(FlaskForm): + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + +class RegisterForm(FlaskForm): + username = StringField('Username', validators=[DataRequired(), Length(3, 80)]) + full_name = StringField('Full Name', validators=[DataRequired(), Length(2, 150)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired(), Length(8, 100)]) + confirm = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) + +class ProfileForm(FlaskForm): + full_name = StringField('Full Name', validators=[DataRequired(), Length(2, 150)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + bio = TextAreaField('Bio', validators=[Optional(), Length(max=1000)]) + +class BookmarkForm(FlaskForm): + item_type = StringField('Type', validators=[DataRequired()]) + item_id = StringField('ID', validators=[DataRequired()]) + note = TextAreaField('Note', validators=[Optional(), Length(max=500)]) + +# ─── Login Manager ──────────────────────────────────────────────────────────── + +@login_manager.user_loader +def load_user(user_id): + return db.session.get(User, int(user_id)) + +# ─── Context Processors ─────────────────────────────────────────────────────── + +@app.context_processor +def inject_globals(): + return { + 'now': datetime.utcnow(), + 'colleges': College.query.order_by(College.name).all(), + } + +# ─── Routes ─────────────────────────────────────────────────────────────────── + +@app.route('/') +def index(): + featured_news = NewsArticle.query.filter_by(featured=True).order_by( + NewsArticle.published_date.desc()).limit(6).all() + if len(featured_news) < 3: + featured_news = NewsArticle.query.order_by( + NewsArticle.published_date.desc()).limit(6).all() + upcoming_events = Event.query.filter( + Event.start_datetime >= datetime.utcnow() + ).order_by(Event.start_datetime).limit(4).all() + recent_research = ResearchCenter.query.limit(4).all() + stats = { + 'fulbright_rank': 1, + 'undergrad_majors': 200, + 'grad_programs': 278, + 'varsity_sports': 36, + 'faculty_count': 7000, + 'undergrad_count': 46820, + 'grad_count': 14000, + 'degree_programs': 500, + 'buckeython_raised': 13, + 'extension_offices': 88, + 'campuses': 6, + 'research_expenditure': 1.3, + } + return render_template('index.html', + featured_news=featured_news, + upcoming_events=upcoming_events, + recent_research=recent_research, + stats=stats) + + +@app.route('/news') +def news(): + q = request.args.get('q', '').strip() + category = request.args.get('category', '') + featured = request.args.get('featured', '') + page = request.args.get('page', 1, type=int) + + query = NewsArticle.query + if q: + query = query.filter( + db.or_( + NewsArticle.title.ilike(f'%{q}%'), + NewsArticle.summary.ilike(f'%{q}%'), + NewsArticle.content.ilike(f'%{q}%'), + NewsArticle.tags.ilike(f'%{q}%'), + )) + if category: + query = query.filter(NewsArticle.category == category) + if featured == '1': + query = query.filter(NewsArticle.featured == True) + + query = query.order_by(NewsArticle.published_date.desc()) + total = query.count() + articles = query.offset((page - 1) * PER_PAGE).limit(PER_PAGE).all() + total_pages = ceil(total / PER_PAGE) if total else 1 + + categories = ['Research', 'Campus Life', 'Faculty', 'Student', 'Athletics', + 'Science', 'Health'] + return render_template('news.html', + articles=articles, + total=total, + page=page, + total_pages=total_pages, + categories=categories, + current_category=category, + q=q, + featured=featured) + + +@app.route('/news/') +def news_article(slug): + article = NewsArticle.query.filter_by(slug=slug).first_or_404() + article.view_count = (article.view_count or 0) + 1 + db.session.commit() + related = NewsArticle.query.filter( + NewsArticle.category == article.category, + NewsArticle.id != article.id + ).order_by(NewsArticle.published_date.desc()).limit(3).all() + return render_template('news_article.html', article=article, related=related) + + +@app.route('/academics') +def academics(): + colleges = College.query.order_by(College.name).all() + total_programs = Program.query.count() + total_depts = Department.query.count() + return render_template('academics.html', + colleges=colleges, + total_programs=total_programs, + total_depts=total_depts) + + +@app.route('/programs') +def programs(): + q = request.args.get('q', '').strip() + college_slug = request.args.get('college', '') + degree = request.args.get('degree', '') + online = request.args.get('online', '') + page = request.args.get('page', 1, type=int) + + query = Program.query + if q: + query = query.filter( + db.or_( + Program.name.ilike(f'%{q}%'), + Program.description.ilike(f'%{q}%'), + )) + if college_slug: + col = College.query.filter_by(slug=college_slug).first() + if col: + query = query.filter(Program.college_id == col.id) + if degree: + query = query.filter(Program.degree_type == degree) + if online == '1': + query = query.filter(Program.is_online == True) + + query = query.order_by(Program.name) + total = query.count() + progs = query.offset((page - 1) * PER_PAGE).limit(PER_PAGE).all() + total_pages = ceil(total / PER_PAGE) if total else 1 + + all_colleges = College.query.order_by(College.name).all() + degree_types = ['BA', 'BS', 'MA', 'MS', 'PhD', 'MPH', 'MBA', 'JD', 'MD', + 'PharmD', 'DVM', 'OD'] + return render_template('programs.html', + programs=progs, + total=total, + page=page, + total_pages=total_pages, + all_colleges=all_colleges, + degree_types=degree_types, + current_college=college_slug, + current_degree=degree, + online=online, + q=q) + + +@app.route('/programs/') +def program_detail(slug): + program = Program.query.filter_by(slug=slug).first_or_404() + related = Program.query.filter( + Program.college_id == program.college_id, + Program.id != program.id + ).limit(4).all() + return render_template('program_detail.html', program=program, related=related) + + +@app.route('/events') +def events(): + q = request.args.get('q', '').strip() + category = request.args.get('category', '') + campus = request.args.get('campus', '') + date_filter = request.args.get('date', 'upcoming') + page = request.args.get('page', 1, type=int) + now = datetime.utcnow() + + query = Event.query + if q: + query = query.filter( + db.or_( + Event.title.ilike(f'%{q}%'), + Event.description.ilike(f'%{q}%'), + Event.location.ilike(f'%{q}%'), + Event.organizer.ilike(f'%{q}%'), + )) + if category: + query = query.filter(Event.category == category) + if campus: + query = query.filter(Event.campus == campus) + if date_filter == 'upcoming': + query = query.filter(Event.start_datetime >= now) + query = query.order_by(Event.start_datetime) + elif date_filter == 'past': + query = query.filter(Event.start_datetime < now) + query = query.order_by(Event.start_datetime.desc()) + elif date_filter == 'today': + today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) + today_end = now.replace(hour=23, minute=59, second=59) + query = query.filter(Event.start_datetime.between(today_start, today_end)) + query = query.order_by(Event.start_datetime) + else: + query = query.order_by(Event.start_datetime) + + total = query.count() + evts = query.offset((page - 1) * PER_PAGE).limit(PER_PAGE).all() + total_pages = ceil(total / PER_PAGE) if total else 1 + + categories = ['Lecture', 'Sports', 'Arts', 'Career', 'Health', 'Social', 'Virtual'] + campuses = ['Columbus', 'Lima', 'Marion', 'Mansfield', 'Newark', 'Wooster'] + return render_template('events.html', + events=evts, + total=total, + page=page, + total_pages=total_pages, + categories=categories, + campuses=campuses, + current_category=category, + current_campus=campus, + date_filter=date_filter, + q=q) + + +@app.route('/events/') +def event_detail(event_id): + event = db.session.get(Event, event_id) + if event is None: + abort(404) + related = Event.query.filter( + Event.category == event.category, + Event.id != event.id, + Event.start_datetime >= datetime.utcnow() + ).order_by(Event.start_datetime).limit(3).all() + return render_template('event_detail.html', event=event, related=related) + + +@app.route('/research') +def research(): + centers = ResearchCenter.query.order_by(ResearchCenter.name).all() + colleges = College.query.order_by(College.name).all() + return render_template('research.html', centers=centers, colleges=colleges) + + +@app.route('/research/') +def research_center(slug): + center = ResearchCenter.query.filter_by(slug=slug).first_or_404() + related = ResearchCenter.query.filter( + ResearchCenter.college_id == center.college_id, + ResearchCenter.id != center.id + ).limit(3).all() + return render_template('research_center.html', center=center, related=related) + + +@app.route('/departments') +def departments(): + all_colleges = College.query.order_by(College.name).all() + depts_by_college = {} + for college in all_colleges: + depts_by_college[college] = Department.query.filter_by( + college_id=college.id).order_by(Department.name).all() + return render_template('departments.html', depts_by_college=depts_by_college) + + +@app.route('/departments/') +def department_detail(slug): + dept = Department.query.filter_by(slug=slug).first_or_404() + faculty_list = Faculty.query.filter_by(department_id=dept.id).order_by(Faculty.name).all() + dept_programs = Program.query.filter_by(department_id=dept.id).all() + return render_template('department_detail.html', + dept=dept, + faculty_list=faculty_list, + programs=dept_programs) + + +@app.route('/admissions') +def admissions(): + undergrad_programs = Program.query.filter( + Program.degree_type.in_(['BA', 'BS']) + ).count() + grad_programs = Program.query.filter( + Program.degree_type.in_(['MA', 'MS', 'PhD', 'MPH', 'MBA', 'JD', 'MD', + 'PharmD', 'DVM', 'OD']) + ).count() + online_programs = Program.query.filter_by(is_online=True).count() + return render_template('admissions.html', + undergrad_programs=undergrad_programs, + grad_programs=grad_programs, + online_programs=online_programs) + + +@app.route('/about') +def about(): + stats = { + 'fulbright_rank': 1, + 'undergrad_majors': 200, + 'grad_programs': 278, + 'varsity_sports': 36, + 'faculty_count': 7000, + 'undergrad_count': 46820, + 'grad_count': 14000, + 'degree_programs': 500, + 'founded': 1870, + 'acres': 1665, + 'campuses': 6, + 'alumni': 600000, + 'extension_offices': 88, + 'buckeython_raised': 13, + 'research_expenditure': 1.3, + 'national_titles': 15, + } + return render_template('about.html', stats=stats) + + +@app.route('/search') +def search(): + q = request.args.get('q', '').strip() + results = {'programs': [], 'news': [], 'events': [], 'faculty': [], + 'research': [], 'athletics': []} + total = 0 + if q: + results['programs'] = Program.query.filter( + db.or_( + Program.name.ilike(f'%{q}%'), + Program.description.ilike(f'%{q}%'), + )).limit(10).all() + results['news'] = NewsArticle.query.filter( + db.or_( + NewsArticle.title.ilike(f'%{q}%'), + NewsArticle.summary.ilike(f'%{q}%'), + NewsArticle.tags.ilike(f'%{q}%'), + )).order_by(NewsArticle.published_date.desc()).limit(10).all() + results['events'] = Event.query.filter( + db.or_( + Event.title.ilike(f'%{q}%'), + Event.description.ilike(f'%{q}%'), + )).limit(10).all() + results['faculty'] = Faculty.query.filter( + db.or_( + Faculty.name.ilike(f'%{q}%'), + Faculty.research_interests.ilike(f'%{q}%'), + Faculty.bio.ilike(f'%{q}%'), + )).limit(10).all() + results['research'] = ResearchCenter.query.filter( + db.or_( + ResearchCenter.name.ilike(f'%{q}%'), + ResearchCenter.description.ilike(f'%{q}%'), + ResearchCenter.focus_areas.ilike(f'%{q}%'), + )).limit(10).all() + results['athletics'] = AthleticTeam.query.filter( + db.or_( + AthleticTeam.name.ilike(f'%{q}%'), + AthleticTeam.sport.ilike(f'%{q}%'), + )).limit(10).all() + total = sum(len(v) for v in results.values()) + return render_template('search.html', q=q, results=results, total=total) + + +@app.route('/faculty') +def faculty(): + q = request.args.get('q', '').strip() + dept_slug = request.args.get('dept', '') + page = request.args.get('page', 1, type=int) + + query = Faculty.query + if q: + query = query.filter( + db.or_( + Faculty.name.ilike(f'%{q}%'), + Faculty.research_interests.ilike(f'%{q}%'), + Faculty.title.ilike(f'%{q}%'), + )) + if dept_slug: + dept = Department.query.filter_by(slug=dept_slug).first() + if dept: + query = query.filter(Faculty.department_id == dept.id) + + query = query.order_by(Faculty.name) + total = query.count() + faculty_list = query.offset((page - 1) * PER_PAGE).limit(PER_PAGE).all() + total_pages = ceil(total / PER_PAGE) if total else 1 + + all_depts = Department.query.order_by(Department.name).all() + return render_template('faculty.html', + faculty_list=faculty_list, + total=total, + page=page, + total_pages=total_pages, + all_depts=all_depts, + current_dept=dept_slug, + q=q) + + +@app.route('/faculty/') +def faculty_profile(slug): + member = Faculty.query.filter_by(slug=slug).first_or_404() + colleagues = [] + if member.department_id: + colleagues = Faculty.query.filter( + Faculty.department_id == member.department_id, + Faculty.id != member.id + ).limit(5).all() + return render_template('faculty_profile.html', member=member, colleagues=colleagues) + + +@app.route('/athletics') +def athletics(): + teams = AthleticTeam.query.order_by(AthleticTeam.sport, AthleticTeam.name).all() + men_teams = [t for t in teams if t.gender == 'Men'] + women_teams = [t for t in teams if t.gender == 'Women'] + coed_teams = [t for t in teams if t.gender == 'Co-ed'] + return render_template('athletics.html', + teams=teams, + men_teams=men_teams, + women_teams=women_teams, + coed_teams=coed_teams) + + +@app.route('/athletics/') +def athletics_team(slug): + team = AthleticTeam.query.filter_by(slug=slug).first_or_404() + related = AthleticTeam.query.filter( + AthleticTeam.gender == team.gender, + AthleticTeam.id != team.id + ).limit(4).all() + return render_template('athletics_team.html', team=team, related=related) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data.lower().strip()).first() + if user and user.check_password(form.password.data): + login_user(user) + next_page = request.args.get('next') + flash('Welcome back, Buckeye!', 'success') + return redirect(next_page or url_for('index')) + flash('Invalid email or password.', 'danger') + return render_template('login.html', form=form) + + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = RegisterForm() + if form.validate_on_submit(): + if User.query.filter_by(email=form.email.data.lower().strip()).first(): + flash('Email already registered.', 'danger') + elif User.query.filter_by(username=form.username.data.strip()).first(): + flash('Username already taken.', 'danger') + else: + user = User( + email=form.email.data.lower().strip(), + username=form.username.data.strip(), + full_name=form.full_name.data.strip(), + ) + user.set_password(form.password.data) + db.session.add(user) + db.session.commit() + login_user(user) + flash('Account created! Welcome to The Ohio State University.', 'success') + return redirect(url_for('index')) + return render_template('register.html', form=form) + + +@app.route('/logout', methods=['GET', 'POST']) +@login_required +def logout(): + logout_user() + flash('You have been logged out.', 'info') + return redirect(url_for('index')) + + +@app.route('/account') +@login_required +def account(): + bookmarks = Bookmark.query.filter_by(user_id=current_user.id).order_by( + Bookmark.created_at.desc()).all() + bookmark_details = [] + for bm in bookmarks: + detail = {'bookmark': bm, 'item': None, 'title': '', 'url': '#'} + if bm.item_type == 'program': + item = db.session.get(Program, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.name + detail['url'] = url_for('program_detail', slug=item.slug) + elif bm.item_type == 'news': + item = db.session.get(NewsArticle, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.title + detail['url'] = url_for('news_article', slug=item.slug) + elif bm.item_type == 'event': + item = db.session.get(Event, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.title + detail['url'] = url_for('event_detail', event_id=item.id) + elif bm.item_type == 'faculty': + item = db.session.get(Faculty, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.name + detail['url'] = url_for('faculty_profile', slug=item.slug) + elif bm.item_type == 'research': + item = db.session.get(ResearchCenter, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.name + detail['url'] = url_for('research_center', slug=item.slug) + elif bm.item_type == 'athletics': + item = db.session.get(AthleticTeam, bm.item_id) + if item: + detail['item'] = item + detail['title'] = item.name + detail['url'] = url_for('athletics_team', slug=item.slug) + bookmark_details.append(detail) + return render_template('account.html', bookmark_details=bookmark_details) + + +@app.route('/bookmark/add', methods=['POST']) +@login_required +def bookmark_add(): + item_type = request.form.get('item_type') + item_id = request.form.get('item_id', type=int) + note = request.form.get('note', '') + if item_type and item_id: + existing = Bookmark.query.filter_by( + user_id=current_user.id, item_type=item_type, item_id=item_id + ).first() + if not existing: + bm = Bookmark(user_id=current_user.id, item_type=item_type, + item_id=item_id, note=note) + db.session.add(bm) + db.session.commit() + flash('Saved to bookmarks.', 'success') + else: + flash('Already bookmarked.', 'info') + next_url = request.form.get('next') or request.referrer or url_for('account') + return redirect(next_url) + + +@app.route('/bookmark/remove', methods=['POST']) +@login_required +def bookmark_remove(): + bookmark_id = request.form.get('bookmark_id', type=int) + if bookmark_id: + bm = db.session.get(Bookmark, bookmark_id) + if bm and bm.user_id == current_user.id: + db.session.delete(bm) + db.session.commit() + flash('Bookmark removed.', 'info') + return redirect(request.referrer or url_for('account')) + + +@app.route('/_health') +def health(): + try: + college_count = College.query.count() + program_count = Program.query.count() + return jsonify({ + 'ok': True, + 'site': 'osu', + 'colleges': college_count, + 'programs': program_count, + }) + except Exception as e: + return jsonify({'ok': False, 'message': str(e)}), 500 + + +@app.errorhandler(404) +def not_found(e): + return render_template('404.html'), 404 + + +@app.errorhandler(500) +def server_error(e): + return render_template('500.html'), 500 + + +# ─── Startup ────────────────────────────────────────────────────────────────── + +with app.app_context(): + db.create_all() + from seed_data import seed + seed() + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 40016)) + app.run(host='0.0.0.0', port=port, debug=False) diff --git a/sites/osu/seed_data.py b/sites/osu/seed_data.py new file mode 100644 index 00000000..9c7787ec --- /dev/null +++ b/sites/osu/seed_data.py @@ -0,0 +1,1038 @@ +#!/usr/bin/env python3 +"""Seed all Ohio State University mirror data. Idempotent.""" +from datetime import datetime, timedelta + + +def seed(): + """Seed all data. Gated on College.query.first() to be idempotent.""" + from app import (db, College, Department, Program, NewsArticle, Event, + ResearchCenter, Faculty, AthleticTeam, User, slugify) + + if College.query.first(): + return + + # ── Helper ──────────────────────────────────────────────────────────────── + def _slug(text, extra=''): + base = slugify(text) + if extra: + base = base + '-' + slugify(extra) + return base + + # ───────────────────────────────────────────────────────────────────────── + # COLLEGES + # ───────────────────────────────────────────────────────────────────────── + college_data = [ + ('Arts and Sciences', 'Dean Patricia Bauer', 1870, 12000, 4000, + 'The College of Arts and Sciences is the intellectual and academic heart of Ohio State. ' + 'It offers over 80 majors and 200 graduate programs spanning the humanities, social sciences, ' + 'natural sciences, and mathematics. The college is home to more than 1,400 faculty and serves ' + 'roughly 16,000 undergraduate and graduate students.'), + ('Fisher College of Business', 'Dean Anil Makhija', 1916, 4500, 1800, + 'Fisher College of Business at Ohio State is consistently ranked among the nation\'s top ' + 'business schools. Fisher offers undergraduate, MBA, specialized master\'s, and doctoral ' + 'programs that prepare students for leadership in a globally connected marketplace.'), + ('Education and Human Ecology', 'Dean Carey Andrzejewski', 1895, 1800, 1200, + 'The College of Education and Human Ecology prepares leaders in education, human development, ' + 'family science, and nutrition. EHE faculty conduct research that improves lives at the ' + 'individual, family, community, and policy levels.'), + ('Engineering', 'Dean Ayanna Howard', 1870, 8000, 4500, + 'The College of Engineering at Ohio State is one of the largest and most comprehensive ' + 'engineering colleges in the United States. With 26 departments and research centers, ' + 'it drives economic growth and technological innovation across Ohio and beyond.'), + ('Food, Agricultural, and Environmental Sciences', 'Dean Cathann Kress', 1870, 3200, 1200, + 'CFAES advances knowledge at the intersection of agriculture, food, environment, and human ' + 'health. The college operates the Ohio Agricultural Research and Development Center and ' + 'the OSU Extension system across all 88 Ohio counties.'), + ('Moritz College of Law', 'Dean Wendy Smooth', 1891, 0, 650, + 'Moritz College of Law is one of the nation\'s leading law schools, offering the JD degree ' + 'and several specialized graduate programs. The college is known for its commitment to public ' + 'service, hands-on clinical education, and cutting-edge legal scholarship.'), + ('Medicine', 'Dean K. Craig Kent', 1914, 0, 900, + 'The Ohio State University College of Medicine is one of the largest medical schools in ' + 'the United States. Affiliated with the Wexner Medical Center and the James Cancer Hospital, ' + 'it is a national leader in medical education, research, and patient care.'), + ('Nursing', 'Dean Bernadette Melnyk', 1914, 600, 400, + 'The College of Nursing advances nursing science and prepares professional nurses for ' + 'leadership roles in health care. It offers BSN, MS, DNP, and PhD programs and is known ' + 'for its focus on wellness, evidence-based practice, and mental health.'), + ('Optometry', 'Dean Karla Zadnik', 1914, 200, 250, + 'The College of Optometry is recognized as one of the finest optometric colleges in the ' + 'world. It offers a four-year OD degree program and conducts pioneering research in ' + 'myopia, glaucoma, and vision rehabilitation.'), + ('Pharmacy', 'Dean Henry Mann', 1885, 300, 350, + 'The College of Pharmacy prepares pharmacists and pharmaceutical scientists to improve ' + 'medication therapy outcomes. Its PharmD program integrates cutting-edge research with ' + 'experiential learning at leading clinical sites across Ohio.'), + ('Public Health', 'Dean Amy Ferketich', 2012, 0, 600, + 'The College of Public Health educates future leaders committed to improving the health ' + 'of communities locally, nationally, and globally. Degree programs in epidemiology, health ' + 'behavior, environmental health, and health services management prepare graduates for ' + 'impact in government, industry, and research.'), + ('Social Work', 'Dean Tom Gregoire', 1914, 300, 500, + 'The College of Social Work is dedicated to advancing social and economic justice and ' + 'improving quality of life for people across the lifespan. Programs emphasize field ' + 'practice, evidence-based interventions, and policy advocacy.'), + ('Veterinary Medicine', 'Dean Rustin Moore', 1885, 400, 500, + 'The College of Veterinary Medicine is consistently ranked among the top vet schools in ' + 'the nation. Its teaching hospital serves tens of thousands of animal patients each year, ' + 'and its researchers pioneer breakthroughs in both animal and human health.'), + ('John Glenn College of Public Affairs', 'Dean Trevor Brown', 1999, 100, 350, + 'The John Glenn College of Public Affairs trains the next generation of public servants, ' + 'policy analysts, and nonprofit leaders. Named for the legendary Ohio astronaut and U.S. ' + 'Senator, the college emphasizes ethics, analytical rigor, and civic engagement.'), + ('Dentistry', 'Dean Kristin Williams', 1890, 0, 400, + 'The College of Dentistry provides comprehensive oral health care and trains outstanding ' + 'dental professionals. Its clinic serves tens of thousands of patients annually, and its ' + 'researchers advance knowledge in oral biology, dental materials, and community oral health.'), + ('Graduate School', 'Dean Sean Carson', 1878, 0, 14000, + 'The Graduate School oversees graduate education across all disciplines at Ohio State. ' + 'It supports master\'s, doctoral, and professional degree programs and fosters the ' + 'interdisciplinary research enterprise of the university.'), + ] + + colleges = {} + for name, dean, founded, ug, gr, desc in college_data: + c = College( + name=name, + slug=slugify(name), + dean=dean, + founded_year=founded, + undergrad_count=ug, + grad_count=gr, + description=desc, + ) + db.session.add(c) + colleges[name] = c + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # DEPARTMENTS + # ───────────────────────────────────────────────────────────────────────── + dept_data = [ + # Arts and Sciences + ('Department of Mathematics', 'Arts and Sciences', 'Dr. James Cogdell', + '(614) 292-4975', '100 Mathematics Building', + 'Mathematics at Ohio State encompasses research in algebra, analysis, geometry, ' + 'topology, logic, and applied mathematics. The department houses internationally ' + 'recognized faculty and produces PhD graduates who lead academia and industry.'), + ('Department of Physics', 'Arts and Sciences', 'Dr. Richard Furnstahl', + '(614) 292-5713', '174 W. 18th Avenue', + 'Physics research at Ohio State spans particle physics, condensed matter, biophysics, ' + 'and astrophysics. The department operates state-of-the-art experimental facilities ' + 'and has strong ties to national laboratories.'), + ('Department of Chemistry and Biochemistry', 'Arts and Sciences', 'Dr. Claudia Turro', + '(614) 292-2133', '100 W. 18th Avenue', + 'Chemistry and Biochemistry at Ohio State integrates teaching and research across ' + 'organic, inorganic, physical, analytical, and biological chemistry subfields.'), + ('Department of Computer Science and Engineering', 'Engineering', 'Dr. Srinivasan Parthasarathy', + '(614) 292-5813', '395 Dreese Lab', + 'CSE at Ohio State is at the forefront of computing research in machine learning, ' + 'security, systems, theory, and human-computer interaction. The department collaborates ' + 'with industry partners and national labs.'), + ('Department of Electrical and Computer Engineering', 'Engineering', 'Dr. Joel Johnson', + '(614) 292-3005', '205 Dreese Lab', + 'ECE research spans signal processing, electromagnetics, VLSI design, power systems, ' + 'and wireless communications. The department has strong industry partnerships and ' + 'state-of-the-art laboratories.'), + ('Department of Mechanical and Aerospace Engineering', 'Engineering', 'Dr. Marcelo Dapino', + '(614) 292-2288', '201 W. 19th Avenue', + 'MAE covers thermodynamics, fluid mechanics, solid mechanics, dynamics, robotics, ' + 'and aerospace systems. The department houses research centers including the Center ' + 'for Automotive Research.'), + ('Department of Economics', 'Arts and Sciences', 'Dr. Bruce Weinberg', + '(614) 292-0552', '1945 N. High Street', + 'The Department of Economics provides rigorous training in microeconomics, ' + 'macroeconomics, econometrics, and applied economics. Research spans labor, health, ' + 'development, public finance, and international economics.'), + ('Department of Psychology', 'Arts and Sciences', 'Dr. Steven Hecht', + '(614) 292-1739', '1835 Neil Avenue Mall', + 'Psychology at Ohio State integrates research and training in clinical, cognitive, ' + 'developmental, neuroscience, quantitative, and social psychology.'), + ('Department of History', 'Arts and Sciences', 'Dr. Stephanie Smith', + '(614) 292-2674', '104 Dulles Hall', + 'The History Department offers programs spanning American, European, African, Asian, ' + 'and world history. Faculty conduct research on topics from medieval Europe to ' + 'contemporary America.'), + ('Department of English', 'Arts and Sciences', 'Dr. Robyn Warhol', + '(614) 292-6065', '164 W. 17th Avenue', + 'English at Ohio State encompasses literary studies, creative writing, rhetoric and ' + 'composition, linguistics, and cultural studies. The department has a distinguished ' + 'tradition of interdisciplinary scholarship.'), + ('Department of Finance', 'Fisher College of Business', 'Dr. Kewei Hou', + '(614) 292-9470', '700 Fisher Hall', + 'Finance at Fisher prepares students for careers in investment banking, asset management, ' + 'corporate finance, and financial research. Research focuses on asset pricing, ' + 'corporate governance, and financial markets.'), + ('Department of Management and Human Resources', 'Fisher College of Business', 'Dr. David Greenberger', + '(614) 292-2311', '700 Fisher Hall', + 'The MHR department addresses critical topics in organizational behavior, human resource ' + 'management, leadership, and strategic management through rigorous research and ' + 'experiential learning.'), + ('Department of Biomedical Informatics', 'Medicine', 'Dr. Philip Payne', + '(614) 293-3600', '1800 Cannon Drive', + 'Biomedical Informatics bridges medicine, nursing, pharmacy, and computing to advance ' + 'health data science and clinical decision support. Faculty lead research in clinical ' + 'natural language processing, patient safety, and precision medicine.'), + ('Department of Epidemiology', 'Public Health', 'Dr. Amy Ferketich', + '(614) 292-0745', '1841 Neil Avenue', + 'Epidemiology faculty study the distribution and determinants of health and disease ' + 'in populations. Specialty areas include cancer, cardiovascular disease, infectious ' + 'disease, reproductive health, and social epidemiology.'), + ('Department of Environmental Engineering', 'Engineering', 'Dr. Linda Weavers', + '(614) 292-2006', '470 Hitchcock Hall', + 'Environmental Engineering addresses water, air, and soil quality challenges using ' + 'science and engineering principles. Research spans water treatment, remediation, ' + 'and sustainable infrastructure design.'), + ] + + departments = {} + for name, college_name, chair, phone, loc, desc in dept_data: + d = Department( + name=name, + slug=slugify(name), + college_id=colleges[college_name].id, + chair=chair, + phone=phone, + location=loc, + description=desc, + ) + db.session.add(d) + departments[name] = d + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # PROGRAMS + # ───────────────────────────────────────────────────────────────────────── + program_data = [ + # Undergrad BS/BA + ('Bachelor of Arts in Mathematics', 'BA', 'Arts and Sciences', 'Department of Mathematics', + 120, 4.0, 'December 1', False, False, + 'The BA in Mathematics provides students with a strong foundation in mathematical ' + 'reasoning and problem-solving. Students take courses in calculus, linear algebra, ' + 'abstract algebra, real analysis, and elective courses in their area of interest.', + 'Calculus sequence; Linear Algebra; Abstract Algebra; Real Analysis; 3 electives in pure or applied math.'), + ('Bachelor of Science in Computer Science and Engineering', 'BS', 'Engineering', 'Department of Computer Science and Engineering', + 130, 4.0, 'February 1', False, False, + 'The BS in CSE prepares students for careers in software engineering, systems design, ' + 'machine learning, and computer science research. The curriculum covers algorithms, ' + 'data structures, operating systems, computer architecture, and specialized electives.', + 'Calculus sequence; Physics; Programming fundamentals; Data Structures; Algorithms; OS; Compilers; Senior capstone.'), + ('Bachelor of Science in Mechanical Engineering', 'BS', 'Engineering', 'Department of Mechanical and Aerospace Engineering', + 132, 4.0, 'February 1', False, False, + 'The BS in Mechanical Engineering at Ohio State covers thermodynamics, fluid dynamics, ' + 'solid mechanics, dynamics, and machine design. Students gain hands-on experience in ' + 'state-of-the-art laboratories and design projects.', + 'Calculus; Physics; Engineering mechanics; Thermodynamics; Fluid mechanics; Materials science; Design capstone.'), + ('Bachelor of Science in Economics', 'BS', 'Arts and Sciences', 'Department of Economics', + 122, 4.0, 'February 1', False, False, + 'The BS in Economics offers rigorous training in economic theory, quantitative methods, ' + 'and applied economics. Graduates pursue careers in consulting, finance, government, ' + 'and economics research.', + 'Micro and macroeconomic theory; Econometrics; Math for economists; 5 field electives.'), + ('Bachelor of Arts in English', 'BA', 'Arts and Sciences', 'Department of English', + 120, 4.0, 'February 1', False, False, + 'The BA in English develops critical reading, research, and writing skills through the ' + 'study of literature, rhetoric, creative writing, and linguistics. Graduates succeed ' + 'in law, business, journalism, education, and many other fields.', + 'Foundations in literary study; Writing seminars; Literature survey courses; Upper-level seminars; Capstone.'), + ('Bachelor of Science in Physics', 'BS', 'Arts and Sciences', 'Department of Physics', + 130, 4.0, 'February 1', False, False, + 'The BS in Physics provides rigorous preparation in classical and modern physics, ' + 'mathematical methods, and experimental techniques. Students develop skills for ' + 'careers in research, engineering, education, and technology.', + 'Calculus-based physics sequence; Math methods; Quantum mechanics; E&M; Thermodynamics; Advanced lab; Capstone.'), + ('Bachelor of Science in Business Administration', 'BS', 'Fisher College of Business', 'Department of Finance', + 121, 4.0, 'December 1', False, False, + 'The BSBA at Fisher prepares students for leadership in diverse business environments. ' + 'Specializations include finance, marketing, management, supply chain, and information systems.', + 'Business core; Accounting; Statistics; Management; Finance; Marketing; Operations; Specialization courses; Capstone.'), + # Graduate + ('Master of Science in Computer Science', 'MS', 'Engineering', 'Department of Computer Science and Engineering', + 30, 2.0, 'December 15', False, False, + 'The MS in CSE at Ohio State offers depth in computer science theory and applications. ' + 'Students choose from thesis and non-thesis options and specialize in areas including ' + 'machine learning, systems, security, or algorithms.', + 'Core graduate CS courses; Thesis or project; 10 credit hours of electives.'), + ('Master of Science in Electrical and Computer Engineering', 'MS', 'Engineering', 'Department of Electrical and Computer Engineering', + 30, 2.0, 'January 15', False, True, + 'The MS in ECE prepares engineers for advanced work in signal processing, communications, ' + 'VLSI, power systems, and more. Both thesis and non-thesis options available.', + 'Graduate core courses; Electives; Thesis or final project.'), + ('Master of Business Administration', 'MBA', 'Fisher College of Business', 'Department of Management and Human Resources', + 60, 2.0, 'April 1', False, False, + 'The Fisher MBA develops business leaders through a rigorous curriculum combining ' + 'management fundamentals with real-world application. Specializations available in ' + 'finance, marketing, entrepreneurship, and operations.', + 'Business core; Leadership skills; Industry-specific electives; Business simulation; Capstone project.'), + ('Doctor of Philosophy in Mathematics', 'PhD', 'Arts and Sciences', 'Department of Mathematics', + 80, 5.0, 'January 1', False, True, + 'The PhD in Mathematics at Ohio State is a research-intensive program that prepares ' + 'students for academic and research careers. Students specialize in algebra, analysis, ' + 'geometry, topology, logic, or applied mathematics.', + 'Graduate coursework; Qualifying exams; Dissertation; Teaching duties.'), + ('Doctor of Philosophy in Physics', 'PhD', 'Arts and Sciences', 'Department of Physics', + 80, 5.5, 'December 1', False, True, + 'The PhD in Physics prepares students for careers in academic research, national ' + 'laboratories, and industry. Students conduct original research in particle physics, ' + 'condensed matter, biophysics, or astrophysics.', + 'Coursework; Qualifying exam; Research rotations; Dissertation.'), + ('Doctor of Philosophy in Computer Science', 'PhD', 'Engineering', 'Department of Computer Science and Engineering', + 80, 5.0, 'December 1', False, True, + 'The PhD in CS at Ohio State is a research-intensive program with national recognition ' + 'in machine learning, security, theoretical computer science, and human-computer ' + 'interaction. Students publish in top venues and collaborate with industry.', + 'Coursework; Candidacy exam; Research; Dissertation; Publications.'), + ('Juris Doctor', 'JD', 'Moritz College of Law', None, + 90, 3.0, 'April 1', False, False, + 'The JD at Moritz College of Law prepares students for practice in any legal field. ' + 'Known for experiential learning, the program offers more than 20 clinics, mock trial ' + 'competitions, and strong placement in law firms, government, and public service.', + 'Legal writing and research; Constitutional law; Contracts; Torts; Civil procedure; Professional responsibility; Electives; Clinical experience.'), + ('Doctor of Medicine', 'MD', 'Medicine', 'Department of Biomedical Informatics', + 0, 4.0, 'October 15', False, False, + 'The MD program at Ohio State College of Medicine offers exceptional training through ' + 'the unique Buckeye Transformative Education in Medicine curriculum. Students benefit ' + 'from early clinical experiences, research opportunities, and connections to the ' + 'nationally ranked Wexner Medical Center.', + 'Pre-clinical foundations; Clinical rotations; Research; Residency preparation.'), + ('Doctor of Pharmacy', 'PharmD', 'Pharmacy', None, + 0, 4.0, 'November 1', False, False, + 'The PharmD at Ohio State prepares pharmacists for clinical, research, and industry ' + 'careers. The curriculum integrates pharmaceutical sciences with patient-centered care ' + 'through experiential learning at outstanding clinical sites.', + 'Pharmaceutical sciences; Pharmacotherapy; Clinical rotations; Advanced practice experiences.'), + ('Doctor of Veterinary Medicine', 'DVM', 'Veterinary Medicine', None, + 0, 4.0, 'October 1', False, False, + 'The DVM program at Ohio State is consistently ranked among the top in the nation. ' + 'Students train at the Veterinary Medical Center, one of the nation\'s premier ' + 'veterinary teaching hospitals, and benefit from research opportunities across ' + 'all animal species.', + 'Biomedical sciences; Clinical sciences; Rotations; Research elective.'), + ('Doctor of Optometry', 'OD', 'Optometry', None, + 0, 4.0, 'October 1', False, False, + 'The OD program at Ohio State prepares optometrists for comprehensive patient care ' + 'in primary care, specialty, and research settings. Students gain extensive clinical ' + 'experience at the OSU Eye Center and affiliated sites.', + 'Optometric sciences; Clinical methods; Patient care rotations; Research.'), + ('Master of Public Health', 'MPH', 'Public Health', 'Department of Epidemiology', + 48, 2.0, 'February 1', True, False, + 'The MPH at Ohio State prepares public health professionals to assess, plan, and ' + 'implement programs that improve community health. Concentrations include epidemiology, ' + 'health behavior, environmental health, and health management.', + 'Core public health competencies; Concentration courses; Practicum; Capstone project.'), + ('Master of Science in Environmental Engineering', 'MS', 'Engineering', 'Department of Environmental Engineering', + 30, 2.0, 'January 15', False, False, + 'The MS in Environmental Engineering addresses water and wastewater treatment, ' + 'air quality, solid waste management, and environmental remediation. Both thesis ' + 'and non-thesis options are available.', + 'Graduate core; Electives; Thesis or project; Professional seminar.'), + ] + + for (name, deg, college_name, dept_name, units, dur, deadline, + is_online, gre, desc, reqs) in program_data: + college_obj = colleges.get(college_name) + dept_obj = departments.get(dept_name) if dept_name else None + slug = slugify(name) + '-' + deg.lower() + p = Program( + name=name, + slug=slug, + degree_type=deg, + college_id=college_obj.id if college_obj else None, + department_id=dept_obj.id if dept_obj else None, + units=units, + duration_years=dur, + application_deadline=deadline, + is_online=is_online, + gre_required=gre, + description=desc, + requirements=reqs, + ) + db.session.add(p) + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # RESEARCH CENTERS + # ───────────────────────────────────────────────────────────────────────── + base_year = datetime(2024, 1, 1) + research_data = [ + ('Translational Data Analytics Institute', 'TDAI', 'Dr. Beth Plale', + 'Arts and Sciences', 2016, + 'Data analytics, Machine learning, Health informatics, Social science', + 'https://tdai.osu.edu', + 'TDAI brings together faculty from across Ohio State to harness the power of data analytics ' + 'to address complex problems in society. The institute supports interdisciplinary research ' + 'in health, agriculture, smart cities, and social sciences.'), + ('Byrd Alzheimer\'s Center and Research Institute', 'Byrd', 'Dr. Douglas Scharre', + 'Medicine', 1987, + 'Alzheimer\'s disease, Dementia, Neuroimaging, Clinical trials', + '', + 'The Byrd Alzheimer\'s Center is dedicated to discovering causes and cures for Alzheimer\'s ' + 'disease and related dementias. Researchers conduct clinical trials and basic science studies ' + 'to advance diagnosis, prevention, and treatment.'), + ('Infectious Disease Institute', 'IDI', 'Dr. Michael Oglesbee', + 'Veterinary Medicine', 2008, + 'Infectious disease, Epidemiology, One Health, Vaccines', + 'https://idi.osu.edu', + 'IDI brings together virologists, bacteriologists, immunologists, and epidemiologists to ' + 'address infectious disease threats to humans, animals, and ecosystems through the One Health ' + 'approach. Research programs span HIV, influenza, SARS-CoV-2, and emerging pathogens.'), + ('Ohio Supercomputer Center', 'OSC', 'Dr. David Bickel', + 'Engineering', 1987, + 'High-performance computing, Scientific computing, Data storage, Visualization', + 'https://www.osc.edu', + 'The Ohio Supercomputer Center is a statewide resource supporting computational research ' + 'at Ohio State and institutions across Ohio. OSC provides high-performance computing, ' + 'data storage, and training to researchers in science, engineering, and the humanities.'), + ('Center for Automotive Research', 'CAR', 'Dr. Giorgio Rizzoni', + 'Engineering', 1991, + 'Electric vehicles, Autonomous driving, Energy storage, Powertrain systems', + 'https://car.osu.edu', + 'CAR partners with automotive industry leaders to advance electrification, autonomy, ' + 'connectivity, and mobility. Research programs address battery systems, vehicle dynamics, ' + 'driver behavior, and sustainable transportation systems.'), + ('Battelle Center for Science, Engineering and Public Policy', 'Battelle', 'Dr. Clay Johnston', + 'John Glenn College of Public Affairs', 2010, + 'Science policy, Technology policy, Energy policy, Climate policy', + '', + 'The Battelle Center examines how science and technology shape public policy choices. ' + 'Faculty and students analyze energy, environment, health, and security policy, ' + 'bridging the gap between scientific evidence and policy action.'), + ('James Cancer Hospital and Solove Research Institute', 'James', 'Dr. William Farrar', + 'Medicine', 1990, + 'Cancer research, Oncology, Clinical trials, Precision medicine', + 'https://cancer.osu.edu', + 'The James Cancer Hospital and Solove Research Institute is Ohio\'s only comprehensive ' + 'cancer center and ranks among the nation\'s top cancer programs. Researchers develop ' + 'novel immunotherapies, targeted treatments, and early detection methods.'), + ('Wexner Medical Center', 'WMC', 'Dr. Hal Paz', + 'Medicine', 1952, + 'Clinical medicine, Medical education, Translational research, Health systems', + 'https://wexnermedical.osu.edu', + 'The Ohio State Wexner Medical Center is a nationally recognized academic medical center ' + 'with hospitals, clinical programs, and research institutes advancing human health. ' + 'Researchers translate scientific discoveries into new diagnostics and therapies.'), + ('Drug Enforcement and Policy Center', 'DEPC', 'Dr. Douglas Berman', + 'Moritz College of Law', 2018, + 'Drug policy, Criminal justice, Marijuana policy, Opioid epidemic', + 'https://depc.osu.edu', + 'DEPC is the leading academic center on drug law and policy, examining enforcement, ' + 'regulation, and reform. Center experts provide evidence-based analysis on topics ' + 'including the opioid crisis, marijuana legalization, and sentencing reform.'), + ('Center for Clean Hydrogen', 'CCH', 'Dr. Yann Guezennec', + 'Engineering', 2022, + 'Hydrogen energy, Fuel cells, Green hydrogen, Energy storage', + '', + 'The Center for Clean Hydrogen advances science and engineering to enable a hydrogen ' + 'economy. Research addresses hydrogen production, storage, transportation, and fuel ' + 'cell technology for transportation and power generation applications.'), + ('Advanced Computing Center for the Arts and Design', 'ACCAD', 'Dr. Maria Palazzi', + 'Arts and Sciences', 1987, + 'Digital arts, Computer animation, Visualization, Motion capture', + 'https://accad.osu.edu', + 'ACCAD is an internationally recognized center for research and practice at the ' + 'intersection of art, design, and computing. Faculty and students develop new forms ' + 'of digital art, animation, interactive media, and scientific visualization.'), + ('Center for Cognitive and Brain Sciences', 'CCBS', 'Dr. Michael DeSchutter', + 'Arts and Sciences', 2007, + 'Neuroscience, Cognitive science, Decision making, Language', + '', + 'CCBS is an interdisciplinary research center addressing fundamental questions about ' + 'the brain and mind. Research programs span perception, attention, memory, decision ' + 'making, language, and social cognition using behavioral, neuroimaging, and computational methods.'), + ('Sustainability Institute', 'SI', 'Dr. Julie Newman', + 'Food, Agricultural, and Environmental Sciences', 2008, + 'Sustainability, Climate change, Campus operations, Environmental policy', + 'https://si.osu.edu', + 'The Sustainability Institute leads Ohio State\'s efforts to advance sustainability ' + 'in research, education, and campus operations. The institute supports interdisciplinary ' + 'research on energy, water, food systems, biodiversity, and climate resilience.'), + ('Chadwick Arboretum and Learning Gardens', 'Chadwick', 'Dr. Susan Pell', + 'Food, Agricultural, and Environmental Sciences', 1980, + 'Plant science, Horticulture, Biodiversity, Sustainable landscapes', + 'https://chadwickarboretum.osu.edu', + 'Chadwick Arboretum is a 60-acre living laboratory on the Columbus campus featuring ' + 'thousands of plant species. The arboretum supports research in plant science, ' + 'sustainable horticulture, and environmental education for the community.'), + ('Center for Biostatistics', 'CBS', 'Dr. Michael Pennell', + 'Public Health', 2001, + 'Biostatistics, Clinical trials, Epidemiology, Statistical genetics', + '', + 'The Center for Biostatistics provides statistical expertise and methodology development ' + 'to support clinical and public health research across Ohio State. Faculty collaborate ' + 'on clinical trials, cohort studies, and genomic data analysis.'), + ] + + rc_map = {} + for (name, short, director, college_name, founded, focus, url, desc) in research_data: + rc = ResearchCenter( + name=name, + slug=slugify(name), + director=director, + college_id=colleges.get(college_name, colleges['Arts and Sciences']).id, + founded_year=founded, + focus_areas=focus, + url=url, + description=desc, + ) + db.session.add(rc) + rc_map[name] = rc + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # FACULTY + # ───────────────────────────────────────────────────────────────────────── + faculty_data = [ + ('Dr. James Cogdell', 'Professor', 'Department of Mathematics', 'cogdell.1@osu.edu', 'MW 724', '(614) 292-4975', + 'Number theory, Automorphic forms, L-functions, Langlands program', + 'Professor Cogdell is a leading number theorist specializing in automorphic forms and the Langlands program. ' + 'He has received numerous awards and fellowships for his research contributions.', False), + ('Dr. Claudia Turro', 'Professor and Chair', 'Department of Chemistry and Biochemistry', 'turro.1@osu.edu', 'Evans 100D', '(614) 292-6567', + 'Inorganic photochemistry, Solar energy conversion, Anticancer agents, Ruthenium complexes', + 'Professor Turro leads research in inorganic photochemistry with applications in solar energy and cancer therapy. ' + 'Her group develops ruthenium-based complexes for photoactivated cancer treatment.', False), + ('Dr. Richard Furnstahl', 'Professor', 'Department of Physics', 'furnstahl.1@osu.edu', 'M2048 Physics Research Building', '(614) 292-4830', + 'Nuclear physics, Quantum chromodynamics, Effective field theory, Machine learning in physics', + 'Professor Furnstahl is a nuclear theorist who uses effective field theory and Bayesian methods ' + 'to study nuclear structure and reactions.', False), + ('Dr. Srinivasan Parthasarathy', 'Professor and Chair', 'Department of Computer Science and Engineering', 'parthasarathy.2@osu.edu', '591 Dreese Lab', '(614) 292-2568', + 'Data mining, Machine learning, Graph mining, Bioinformatics', + 'Professor Parthasarathy is an internationally recognized expert in data mining, machine learning, ' + 'and graph analytics. His group develops algorithms for large-scale data analysis with applications ' + 'in health care, social networks, and genomics.', False), + ('Dr. Marcelo Dapino', 'Professor and Honda R&D Americas Chair', 'Department of Mechanical and Aerospace Engineering', 'dapino.1@osu.edu', '201 W. 19th Avenue', '(614) 292-9138', + 'Smart materials, Vibration control, Automotive engineering, Magnetostrictive actuators', + 'Professor Dapino is a leading researcher in smart materials and structural acoustics, ' + 'with applications in automotive NVH and adaptive structures.', False), + ('Dr. Bruce Weinberg', 'Professor', 'Department of Economics', 'weinberg.27@osu.edu', '422 Arps Hall', '(614) 292-0553', + 'Labor economics, Innovation, Science of science, Health economics', + 'Professor Weinberg studies the economics of innovation, labor markets, and health. ' + 'His research on the relationship between age and scientific productivity has received ' + 'wide attention in academia and the popular press.', False), + ('Dr. Philip Payne', 'Professor and Chair', 'Department of Biomedical Informatics', 'payne.38@osu.edu', '1800 Cannon Drive', '(614) 293-3600', + 'Biomedical informatics, Clinical NLP, Precision medicine, Learning health systems', + 'Professor Payne leads the Department of Biomedical Informatics and is a pioneer in ' + 'data-driven approaches to clinical decision support and precision medicine.', False), + ('Dr. Amy Ferketich', 'Professor and Dean', 'Department of Epidemiology', 'ferketich.1@osu.edu', '250 Cunz Hall', '(614) 292-0745', + 'Tobacco control, Cancer epidemiology, Health disparities, Behavioral interventions', + 'Dean Ferketich is a nationally recognized tobacco control researcher who has led ' + 'population-based studies on smoking cessation, tobacco marketing, and health disparities.', False), + ('Dr. David Greenberger', 'Professor Emeritus', 'Department of Management and Human Resources', 'greenberger.1@osu.edu', '700 Fisher Hall', '(614) 292-0040', + 'Organizational behavior, Leadership, Work and family, Entrepreneurship', + 'Professor Greenberger is a pioneer in organizational behavior research and has made ' + 'foundational contributions to our understanding of leadership and organizational control.', True), + ('Dr. Kewei Hou', 'Professor', 'Department of Finance', 'hou.28@osu.edu', '750 Fisher Hall', '(614) 292-0552', + 'Asset pricing, Empirical finance, Factor models, Market anomalies', + 'Professor Hou is one of the leading empirical finance researchers of his generation, ' + 'known for the q-factor model of stock returns and research on market anomalies.', False), + ('Dr. Douglas Scharre', 'Professor and Director', 'Department of Epidemiology', 'scharre.1@osu.edu', '395 W. 12th Avenue', '(614) 293-4969', + 'Alzheimer\'s disease, Cognitive assessment, Dementia treatment, Brain aging', + 'Professor Scharre directs the Division of Cognitive Neurology and the Byrd Alzheimer\'s Center. ' + 'He developed the widely used Self-Administered Gerocognitive Exam (SAGE) for early ' + 'detection of cognitive impairment.', False), + ('Dr. Giorgio Rizzoni', 'Professor and Director', 'Department of Mechanical and Aerospace Engineering', 'rizzoni.1@osu.edu', '930 Kinnear Road', '(614) 292-0734', + 'Electric vehicles, Energy management, Hybrid powertrains, Control systems', + 'Professor Rizzoni is a global authority on electrified transportation and energy management ' + 'for hybrid and electric vehicles. He directs the Center for Automotive Research and has ' + 'led numerous multi-million dollar collaborative projects with automotive partners.', False), + ('Dr. Beth Plale', 'Professor and Executive Director', 'Department of Computer Science and Engineering', 'plale.1@osu.edu', '550 Dreese Lab', '(614) 292-1234', + 'Data science, Provenance, Research data management, Machine learning', + 'Professor Plale is Executive Director of the Translational Data Analytics Institute and ' + 'leads research in data science, provenance, and research data management.', False), + ('Dr. Linda Weavers', 'Professor and Chair', 'Department of Environmental Engineering', 'weavers.1@osu.edu', '470 Hitchcock Hall', '(614) 292-2006', + 'Water treatment, Sonochemistry, Environmental remediation, Emerging contaminants', + 'Professor Weavers is a leading expert in water treatment and sonochemical processes ' + 'for environmental remediation. Her research addresses treatment of emerging contaminants ' + 'including pharmaceuticals and PFAS.', False), + ('Dr. Douglas Berman', 'Professor and Director', 'Department of Economics', 'berman.43@osu.edu', '55 W. 12th Avenue', '(614) 292-5925', + 'Criminal law, Sentencing, Drug policy, Prison policy', + 'Professor Berman is the nation\'s leading academic expert on federal sentencing law and ' + 'drug policy reform. He founded and edits the widely read Sentencing Law and Policy blog ' + 'and directs the Drug Enforcement and Policy Center.', False), + ] + + for (name, title, dept_name, email, office, phone, interests, bio, emeritus) in faculty_data: + dept_obj = departments.get(dept_name) + m = Faculty( + name=name, + slug=slugify(name), + title=title, + department_id=dept_obj.id if dept_obj else None, + email=email, + office=office, + phone=phone, + research_interests=interests, + bio=bio, + is_emeritus=emeritus, + ) + db.session.add(m) + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # ATHLETIC TEAMS + # ───────────────────────────────────────────────────────────────────────── + team_data = [ + ('Ohio State Buckeyes Football', 'Football', 'Men', 'Ryan Day', 'Ohio Stadium (Horseshoe)', 8, '11-2'), + ('Ohio State Buckeyes Men\'s Basketball', 'Basketball', 'Men', 'Jake Diebler', 'Value City Arena', 0, '14-17'), + ('Ohio State Buckeyes Women\'s Basketball', 'Basketball', 'Women', 'Kevin McGuff', 'Value City Arena', 0, '21-12'), + ('Ohio State Buckeyes Baseball', 'Baseball', 'Men', 'Bill Mosiello', 'Bill Davis Stadium', 0, '35-22'), + ('Ohio State Buckeyes Softball', 'Softball', 'Women', 'Kelly Kovach Schoenly', 'Buckeye Field', 0, '26-25'), + ('Ohio State Buckeyes Men\'s Soccer', 'Soccer', 'Men', 'Brian Maisonneuve', 'Jesse Owens Memorial Stadium', 0, '7-10-3'), + ('Ohio State Buckeyes Women\'s Soccer', 'Soccer', 'Women', 'Lori Walker', 'Jesse Owens Memorial Stadium', 0, '13-7-3'), + ('Ohio State Buckeyes Men\'s Swimming & Diving', 'Swimming & Diving', 'Men', 'Bill Dorenkott', 'McCorkle Aquatic Pavilion', 12, '—'), + ('Ohio State Buckeyes Women\'s Swimming & Diving', 'Swimming & Diving', 'Women', 'Bill Dorenkott', 'McCorkle Aquatic Pavilion', 11, '—'), + ('Ohio State Buckeyes Men\'s Track & Field', 'Track & Field', 'Men', 'Ed Lomonaco', 'Jesse Owens Memorial Stadium', 0, '—'), + ('Ohio State Buckeyes Women\'s Track & Field', 'Track & Field', 'Women', 'Ed Lomonaco', 'Jesse Owens Memorial Stadium', 0, '—'), + ('Ohio State Buckeyes Volleyball', 'Volleyball', 'Women', 'Jen Flynn Oldenburg', 'Covelli Center', 0, '20-10'), + ('Ohio State Buckeyes Wrestling', 'Wrestling', 'Men', 'Tom Ryan', 'Covelli Center', 8, '17-4'), + ('Ohio State Buckeyes Men\'s Tennis', 'Tennis', 'Men', 'Ty Tucker', 'Ty Tucker Tennis Center', 0, '14-11'), + ('Ohio State Buckeyes Women\'s Tennis', 'Tennis', 'Women', 'Melissa Schaub', 'Ty Tucker Tennis Center', 0, '16-9'), + ('Ohio State Buckeyes Men\'s Golf', 'Golf', 'Men', 'Jay Moseley', 'OSU Golf Club', 0, '—'), + ('Ohio State Buckeyes Women\'s Golf', 'Golf', 'Women', 'Therese Hession', 'OSU Golf Club', 0, '—'), + ('Ohio State Buckeyes Men\'s Gymnastics', 'Gymnastics', 'Men', 'Miles Avery', 'Covelli Center', 0, '—'), + ('Ohio State Buckeyes Women\'s Gymnastics', 'Gymnastics', 'Women', 'Bob Fetter', 'Covelli Center', 0, '—'), + ('Ohio State Buckeyes Ice Hockey', 'Ice Hockey', 'Men', 'Steve Rohlik', 'Value City Arena', 0, '21-13-2'), + ('Ohio State Buckeyes Men\'s Lacrosse', 'Lacrosse', 'Men', 'Nick Myers', 'Ohio Stadium (turf)', 0, '14-5'), + ('Ohio State Buckeyes Women\'s Lacrosse', 'Lacrosse', 'Women', 'Nikki Hanigan', 'Buckeye Field (lacrosse)', 0, '8-10'), + ('Ohio State Buckeyes Field Hockey', 'Field Hockey', 'Women', 'Jarred Martin', 'Buckeye Field', 0, '12-8'), + ('Ohio State Buckeyes Rowing', 'Rowing', 'Women', 'Emanuele Catasta', 'Griggs Reservoir', 0, '—'), + ('Ohio State Buckeyes Fencing', 'Fencing', 'Men', 'George Shutt', 'RPAC', 2, '—'), + ('Ohio State Buckeyes Women\'s Fencing', 'Fencing', 'Women', 'George Shutt', 'RPAC', 0, '—'), + ] + + for (name, sport, gender, coach, venue, titles, record) in team_data: + t = AthleticTeam( + name=name, + slug=slugify(name), + sport=sport, + gender=gender, + conference='Big Ten', + coach=coach, + home_venue=venue, + national_titles=titles, + recent_record=record, + ) + db.session.add(t) + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # NEWS ARTICLES + # ───────────────────────────────────────────────────────────────────────── + now = datetime(2024, 10, 15) + articles = [ + ('Ohio State Researchers Develop Breakthrough Cancer Immunotherapy', 'Research', + 'Jody Sheridan', now - timedelta(days=2), True, + 'Ohio State, immunotherapy, cancer, James Cancer Hospital, research', + 'A team of Ohio State researchers has achieved a major breakthrough in cancer immunotherapy, ' + 'developing a novel approach that could improve outcomes for patients with hard-to-treat solid tumors.', + 'Ohio State researchers led by Dr. William Farrar at the James Cancer Hospital and Solove Research Institute ' + 'have developed a new CAR-T cell therapy that targets multiple tumor antigens simultaneously, ' + 'addressing one of the key limitations of current immunotherapy approaches.\n\n' + 'In preclinical studies, the multi-antigen approach demonstrated a 73 percent reduction in tumor burden ' + 'compared to conventional single-antigen CAR-T therapy. Researchers are now planning a Phase I clinical ' + 'trial expected to begin enrollment in early 2025.\n\n' + '"This represents a fundamental advance in how we design cellular therapies for solid tumors," ' + 'said Dr. Farrar. "By targeting multiple antigens, we make it much harder for cancer cells to ' + 'develop resistance through antigen escape."\n\n' + 'The research was published in Nature Medicine and was supported by grants from the National Cancer ' + 'Institute and the Ohio State University Comprehensive Cancer Center.'), + ('Ohio State Football Ranked in Top 5 Heading into Conference Play', 'Athletics', + 'Mike Cardamone', now - timedelta(days=3), True, + 'football, Buckeyes, Big Ten, ranking, Ryan Day', + 'The Ohio State Buckeyes football team has climbed into the top five of both major polls ' + 'as they enter the heart of Big Ten Conference play.', + 'The Ohio State Buckeyes have moved into the top five of the AP Poll and the Coaches Poll ' + 'after a dominant performance in their non-conference schedule. Coach Ryan Day\'s squad ' + 'has outscored opponents by an average of 34 points per game.\n\n' + '"We\'re playing complementary football right now," Day said at his Monday press conference. ' + '"The offense is efficient, the defense is flying around, and the special teams have been outstanding."\n\n' + 'Quarterback Will Howard has emerged as a Heisman Trophy candidate, completing 72 percent of ' + 'his passes for 1,847 yards and 18 touchdowns against just two interceptions.\n\n' + 'The Buckeyes host Michigan State this Saturday at Ohio Stadium, with kickoff set for noon ' + 'on Fox. The game is a sold-out affair, with over 105,000 fans expected to fill the Horseshoe.'), + ('Fisher College of Business Launches New Sustainability MBA Track', 'Campus Life', + 'OSU News Staff', now - timedelta(days=5), False, + 'Fisher, MBA, sustainability, business, environment', + 'Fisher College of Business has unveiled a new MBA specialization in Sustainable Business, ' + 'responding to growing demand from employers for business leaders with expertise in ESG.', + 'Fisher College of Business has announced the launch of a new Sustainable Business specialization ' + 'within its full-time MBA program, beginning in the 2025-2026 academic year.\n\n' + 'The specialization will allow MBA students to develop expertise in environmental, social, and ' + 'governance (ESG) strategy, sustainable supply chains, green finance, and corporate sustainability reporting.\n\n' + '"Employers are increasingly looking for business leaders who understand how sustainability creates ' + 'long-term value," said Dean Anil Makhija. "This specialization gives our students a competitive ' + 'advantage in a rapidly evolving landscape."\n\n' + 'The program includes case studies from Fortune 500 companies, a sustainability consulting practicum ' + 'with Ohio-based organizations, and access to Fisher\'s extensive alumni network in sustainable business.'), + ('Ohio Supercomputer Center Upgrades to Exascale-Class Computing', 'Research', + 'OSU News Staff', now - timedelta(days=7), False, + 'Ohio Supercomputer Center, HPC, computing, research infrastructure', + 'The Ohio Supercomputer Center has completed a major infrastructure upgrade, bringing ' + 'near-exascale computing capabilities to Ohio researchers.', + 'The Ohio Supercomputer Center has announced the successful deployment of Ascend, a new ' + 'high-performance computing cluster that dramatically expands computational capacity for ' + 'Ohio researchers.\n\n' + 'Ascend features 680 compute nodes with NVIDIA H100 GPUs, delivering over 50 petaflops of ' + 'AI-optimized computing power — a tenfold increase over the previous system. The system also ' + 'includes 10 petabytes of high-speed parallel storage.\n\n' + '"Ascend positions Ohio at the forefront of academic computing," said Director David Bickel. ' + '"Researchers can now tackle AI, climate modeling, drug discovery, and genomics problems ' + 'that were previously out of reach."'), + ('Ohio State Alumnus Appointed to NASA Administrator Role', 'Faculty', + 'OSU News Staff', now - timedelta(days=9), False, + 'alumni, NASA, space, science, engineering', + 'An Ohio State University alumnus with degrees in aerospace engineering and public policy ' + 'has been appointed to a senior leadership role at the National Aeronautics and Space Administration.', + 'Dr. James Crawford, who earned his BS in Aerospace Engineering from Ohio State in 1994 and his ' + 'MPH from the John Glenn College of Public Affairs in 2002, has been appointed as Deputy Associate ' + 'Administrator for Research at NASA.\n\n' + '"Ohio State gave me the foundation to dream big and the tools to make those dreams real," ' + 'Crawford said upon his appointment. "I carry the Buckeye spirit into everything I do."'), + ('College of Engineering Receives $50M Federal Grant for Hydrogen Research', 'Research', + 'College of Engineering Communications', now - timedelta(days=11), True, + 'engineering, hydrogen, energy, federal grant, NSF, research', + 'The Ohio State College of Engineering has been awarded a $50 million Department of Energy ' + 'grant to establish a national center for clean hydrogen research.', + 'The Ohio State College of Engineering has secured a $50 million grant from the Department of ' + 'Energy to establish the National Center for Clean Hydrogen Technologies, led by Professor ' + 'Yann Guezennec of the Center for Clean Hydrogen.\n\n' + 'The center will bring together researchers from engineering, chemistry, environmental engineering, ' + 'and public policy to accelerate the transition to a hydrogen economy.\n\n' + '"Hydrogen is one of the most promising pathways to deep decarbonization," said Dean Ayanna Howard. ' + '"This investment reflects Ohio State\'s leadership in clean energy research and positions us to ' + 'deliver real-world impact at scale."'), + ('Moritz College of Law Hosts National Symposium on Artificial Intelligence and Law', 'Campus Life', + 'Moritz Communications', now - timedelta(days=14), False, + 'law, AI, artificial intelligence, symposium, Moritz', + 'Moritz College of Law welcomed leading legal scholars, judges, and technologists for a ' + 'two-day national symposium on the intersection of artificial intelligence and legal practice.', + 'Moritz College of Law hosted more than 200 legal scholars, practicing attorneys, federal judges, ' + 'and technology experts at its inaugural AI and Law Symposium, exploring how machine learning ' + 'is transforming legal research, evidence, and decision-making.\n\n' + 'Keynote speakers included a federal circuit court judge who spoke about AI-assisted legal research, ' + 'and a leading AI ethicist who addressed bias and fairness in algorithmic decision systems.\n\n' + 'Symposium papers will be published in a special issue of the Ohio State Law Journal in spring 2025.'), + ('Ohio State Named Among Top 20 Public Universities by U.S. News', 'Campus Life', + 'OSU News Staff', now - timedelta(days=16), True, + 'ranking, U.S. News, public university, research', + 'The Ohio State University has been ranked among the top 20 public universities in the ' + 'United States in the latest U.S. News & World Report Best Colleges rankings.', + 'Ohio State maintained its position among the top 20 public universities in the country according ' + 'to the 2025 U.S. News & World Report Best Colleges rankings, continuing its streak of ' + 'consistent improvement over the past decade.\n\n' + 'Several individual programs also saw strong rankings: Computer Science moved into the top 15 ' + 'nationally; the Moritz College of Law climbed to #26; and the Fisher College of Business MBA ' + 'program is now ranked #28 in the nation.\n\n' + '"These rankings reflect the hard work of our faculty, staff, and students," said Provost Melissa Gilliam. ' + '"But more importantly, they reflect our commitment to providing a world-class education and ' + 'conducting research that matters."'), + ('TDAI Launches Interdisciplinary Health Data Science Training Program', 'Research', + 'TDAI Communications', now - timedelta(days=18), False, + 'TDAI, data science, health, training, interdisciplinary', + 'The Translational Data Analytics Institute has launched a new graduate training program ' + 'that brings together doctoral students from medicine, public health, statistics, and computer science.', + 'The Translational Data Analytics Institute has launched the Health Data Science Training Program, ' + 'a multi-year initiative funded by the National Institutes of Health to train the next generation ' + 'of health data scientists.\n\n' + 'The program provides 15 doctoral fellows per cohort with interdisciplinary coursework, mentored ' + 'research experiences, and professional development in communication, ethics, and entrepreneurship.\n\n' + '"Health data science requires expertise that no single discipline can provide," said Director Beth Plale. ' + '"This program creates researchers who can bridge clinical medicine, public health, and advanced computing."'), + ('Buckeyes Wrestling Team Ranked No. 1 in the Nation', 'Athletics', + 'OSU Athletics Communications', now - timedelta(days=20), False, + 'wrestling, ranking, national, Buckeyes, Tom Ryan', + 'The Ohio State wrestling team has opened the season as the No. 1 ranked team in the ' + 'nation, setting sights on a record-breaking national championship run.', + 'Ohio State wrestling began the 2024-25 season ranked No. 1 in the country by InterMat ' + 'and FloWrestling, led by a roster of 11 nationally ranked wrestlers including three ' + 'returning All-Americans.\n\n' + '"This team has the talent and the hunger to be special," said head coach Tom Ryan, who ' + 'has led the Buckeyes to 8 national championships. "But rankings don\'t win titles — ' + 'hard work and execution do."'), + ('Ohio State Sets Record for Research Expenditures at $1.3 Billion', 'Research', + 'Office of Research Communications', now - timedelta(days=22), True, + 'research, expenditure, record, funding, grants', + 'Ohio State has reached a historic milestone, surpassing $1.3 billion in annual research ' + 'expenditures for the first time in the university\'s 154-year history.', + 'The Ohio State University has reported a record $1.3 billion in research expenditures for ' + 'fiscal year 2024, according to data compiled by the Office of Research. This represents a ' + 'seven percent increase over the previous year and marks the university\'s highest-ever ' + 'research investment.\n\n' + 'Funding came from federal agencies including NIH, NSF, DOE, and DOD; state of Ohio; ' + 'industry partners; and private foundations.\n\n' + '"This milestone demonstrates the remarkable breadth and quality of research happening at ' + 'Ohio State," said President Ted Carter. "Our faculty are tackling the challenges that ' + 'matter most — cancer, climate, artificial intelligence, and so much more."'), + ('College of Nursing Launches Mental Health Initiative for Students', 'Health', + 'College of Nursing Communications', now - timedelta(days=25), False, + 'nursing, mental health, students, wellness, Bernadette Melnyk', + 'The College of Nursing, led by Dean Bernadette Melnyk, has launched a comprehensive ' + 'mental health and wellness initiative targeting Ohio State\'s student population.', + 'The Ohio State College of Nursing has launched "Buckeye Wellness," a university-wide ' + 'evidence-based program designed to improve mental health, well-being, and academic ' + 'outcomes for students.\n\n' + 'The program, developed by Dean Bernadette Melnyk and her team, provides students with ' + 'a seven-week cognitive behavioral skills-building intervention via a mobile application, ' + 'peer support groups, and faculty wellness champions.\n\n' + 'Early pilot data showed a 22 percent reduction in depression and anxiety symptoms among ' + 'participating students and a significant improvement in GPAs compared to a control group.'), + ('Ohio State Hosts International Climate Summit Ahead of COP30', 'Research', + 'Sustainability Institute', now - timedelta(days=28), False, + 'climate, sustainability, international, COP, environment', + 'The Ohio State Sustainability Institute welcomed climate researchers and policymakers ' + 'from 40 countries for a pre-COP30 research summit at the Columbus campus.', + 'Ohio State\'s Sustainability Institute hosted the Global Universities Climate Summit, ' + 'bringing together 300 climate scientists, economists, engineers, and policymakers from ' + '40 nations to share research and develop recommendations ahead of the UN Climate ' + 'Conference (COP30).\n\n' + '"Universities play a unique role in the climate challenge — we produce the knowledge ' + 'and the people who will deliver solutions," said Institute Director Dr. Julie Newman. ' + '"This summit strengthens the global network of researchers committed to a just and ' + 'sustainable future."\n\n' + 'Summit participants issued a joint statement calling for accelerated decarbonization ' + 'of electricity systems, food security investments, and equitable climate finance.'), + ('Department of Computer Science Launches AI Ethics Certificate', 'Student', + 'CSE Department Communications', now - timedelta(days=31), False, + 'CSE, AI, ethics, certificate, students', + 'The Department of Computer Science and Engineering has introduced a new undergraduate ' + 'certificate in Artificial Intelligence Ethics, open to students across all colleges.', + 'Ohio State\'s Department of Computer Science and Engineering has launched an undergraduate ' + 'Certificate in AI Ethics, a 15-credit interdisciplinary program available to students in ' + 'any major.\n\n' + 'The certificate curriculum draws on coursework from computer science, philosophy, law, ' + 'sociology, and public policy, preparing students to develop and deploy AI systems ' + 'responsibly.\n\n' + '"AI is being embedded into every sector of society, and we need graduates who understand ' + 'both the technical and ethical dimensions," said Department Chair Dr. Srinivasan Parthasarathy.'), + ('Ohio State Veterinary Medical Center Treats Record Number of Patients', 'Health', + 'College of Veterinary Medicine', now - timedelta(days=35), False, + 'veterinary, Vet Medical Center, patients, animals, care', + 'The Ohio State Veterinary Medical Center has seen a record 86,000 patient visits in ' + 'the past year, reinforcing its reputation as one of the nation\'s premier veterinary ' + 'teaching hospitals.', + 'The Ohio State University Veterinary Medical Center reported 86,231 patient visits ' + 'in fiscal year 2024, a 12 percent increase over the previous year and a new all-time record.\n\n' + 'The VMC offers specialty services across cardiology, dermatology, emergency medicine, ' + 'neurology, oncology, surgery, and more than 25 other disciplines for companion animals, ' + 'horses, and farm animals.\n\n' + '"Our growth reflects both the exceptional quality of our clinical teams and the trust ' + 'that pet owners across the region place in Ohio State," said Dean Rustin Moore.'), + ('Ohio State Announces New Partnership with Nationwide Children\'s Hospital', 'Health', + 'OSU Medical Center Communications', now - timedelta(days=40), False, + 'Nationwide Children\'s, partnership, pediatrics, medical center, research', + 'Ohio State and Nationwide Children\'s Hospital have formalized a landmark partnership ' + 'to advance pediatric research, education, and clinical care.', + 'The Ohio State University and Nationwide Children\'s Hospital have signed a comprehensive ' + 'partnership agreement that strengthens research, training, and patient care collaborations ' + 'between the two institutions.\n\n' + 'Under the agreement, Ohio State medical faculty will hold joint appointments at Nationwide ' + 'Children\'s, and residents in pediatrics will have expanded training opportunities at both ' + 'institutions.\n\n' + '"Together, Ohio State and Nationwide Children\'s are a powerhouse for children\'s health ' + 'in this region and nationally," said OSU President Ted Carter.'), + ('Buckeyes Swimming and Diving Teams Win Big Ten Championships', 'Athletics', + 'OSU Athletics Communications', now - timedelta(days=45), True, + 'swimming, diving, Big Ten, championship, Buckeyes', + 'Ohio State\'s men\'s and women\'s swimming and diving teams both captured Big Ten ' + 'Conference championships in dramatic fashion at the annual championships meet.', + 'Ohio State\'s swimming and diving programs claimed a sweep of the Big Ten championships, ' + 'with both the men\'s and women\'s teams winning conference titles for the second consecutive year.\n\n' + 'The men\'s team finished with 1,342.5 points, edging Michigan by 43 points, ' + 'while the women\'s team dominated with a 260-point margin of victory.\n\n' + 'Coach Bill Dorenkott\'s program has now won a combined 23 Big Ten championships.'), + ('Graduate School Expands Fellowship Opportunities for Doctoral Students', 'Student', + 'Graduate School Communications', now - timedelta(days=50), False, + 'graduate school, fellowship, doctoral, funding, PhD', + 'Ohio State\'s Graduate School has announced a $25 million expansion of fellowship ' + 'funding for doctoral students, aiming to increase diversity and reduce time to degree.', + 'Ohio State\'s Graduate School has announced the Buckeye Graduate Excellence Initiative, ' + 'a $25 million commitment over five years to expand fellowship support for doctoral students.\n\n' + 'The initiative will fund 150 additional university fellowships annually, with a particular ' + 'emphasis on recruiting students from underrepresented backgrounds and high-priority ' + 'research areas including AI, climate science, and biomedical engineering.\n\n' + '"Graduate students are the engine of our research enterprise," said Dean Sean Carson. ' + '"This investment ensures Ohio State can recruit and support the best and brightest from ' + 'across the country and the world."'), + ('Ohio State Extension Celebrates 100 Years of Service to Ohio Communities', 'Campus Life', + 'OSU Extension Communications', now - timedelta(days=55), False, + 'extension, Ohio, community, centennial, agriculture', + 'Ohio State University Extension is marking its centennial year with events across ' + 'all 88 Ohio counties, celebrating a century of research-based service to Ohioans.', + 'Ohio State University Extension is celebrating its 100th anniversary as an official ' + 'federal-state-local partnership, with events and programs planned across all 88 ' + 'Ohio counties throughout the academic year.\n\n' + 'Extension educators serve nearly every Ohio family through programs in agriculture, ' + 'family and consumer sciences, 4-H youth development, and community development.\n\n' + '"Extension connects the resources of Ohio State University directly to Ohioans wherever ' + 'they live," said College Dean Cathann Kress. "For 100 years, we have been a trusted ' + 'partner in making Ohio stronger."'), + ('Ohio State Partners with Intel on $200M Semiconductor Research Initiative', 'Research', + 'College of Engineering Communications', now - timedelta(days=60), True, + 'Intel, semiconductor, engineering, partnership, industry', + 'Ohio State University and Intel Corporation have announced a landmark $200 million ' + 'research partnership focused on next-generation semiconductor technology.', + 'Ohio State and Intel Corporation have announced a $200 million, 10-year research ' + 'partnership to advance semiconductor science and engineering.\n\n' + 'The collaboration will support research in chip design, manufacturing, materials science, ' + 'and workforce development, leveraging Intel\'s planned Ohio semiconductor fabrication ' + 'facilities in Licking County.\n\n' + '"This partnership is transformative for Ohio State and for the state of Ohio," said ' + 'Dean Ayanna Howard. "It connects world-class academic research directly to the largest ' + 'semiconductor investment in American history."\n\n' + 'The agreement includes joint faculty appointments, student internship pipelines, shared ' + 'laboratory facilities, and collaborative research grants.'), + ] + + for (title, cat, author, pub_date, featured, tags, summary, content) in articles: + a = NewsArticle( + title=title, + slug=slugify(title), + category=cat, + author=author, + published_date=pub_date, + featured=featured, + tags=tags, + summary=summary, + content=content, + view_count=0, + ) + db.session.add(a) + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # EVENTS + # ───────────────────────────────────────────────────────────────────────── + future = datetime(2024, 11, 1) + events = [ + ('Buckeyes vs. Michigan State Football', 'Sports', + future + timedelta(days=2), future + timedelta(days=2, hours=3, minutes=30), + 'Ohio Stadium', 'Ohio Stadium (The Horseshoe)', 'Columbus', 'OSU Athletics', False, 'Varies', + 'Come out to the Horseshoe as the Buckeyes take on the Michigan State Spartans in a key Big Ten ' + 'matchup. Enjoy pre-game festivities starting two hours before kickoff, including live music and ' + 'alumni tailgating. This is a sold-out game — tickets required.'), + ('Ohio State Research Forum: AI in Health Care', 'Lecture', + future + timedelta(days=5), future + timedelta(days=5, hours=2), + 'Biomedical Research Tower, Room 105', 'Biomedical Research Tower', 'Columbus', 'TDAI', True, 'Free', + 'Leading researchers from medicine, nursing, and computer science will present their work on ' + 'applying artificial intelligence to clinical decision support, medical imaging analysis, ' + 'and population health management. Lunch will be provided for registered attendees.'), + ('Annual Buckeye Career Fair — Engineering and Technology', 'Career', + future + timedelta(days=8), future + timedelta(days=8, hours=5), + 'Ohio Union', 'Ohio Union', 'Columbus', 'Engineering Career Services', True, 'Free', + 'Over 200 companies recruiting Ohio State students for internships, co-ops, and full-time ' + 'positions in engineering, technology, and related fields. Dress professionally and bring ' + 'multiple copies of your resume. Business casual or professional attire required.'), + ('Wexner Arts Center Performance: International Dance Festival', 'Arts', + future + timedelta(days=10), future + timedelta(days=10, hours=2), + 'Wexner Center for the Arts', 'Mershon Auditorium', 'Columbus', 'Wexner Center', False, '$15-35', + 'The Wexner Center\'s International Dance Festival presents world-renowned dance companies ' + 'in an evening of contemporary and classical works. This year features companies from ' + 'Brazil, South Korea, and France. Tickets available at the Wexner Center box office.'), + ('Ohio State Farmers Market', 'Social', + future + timedelta(days=12), future + timedelta(days=12, hours=3), + 'Tuttle Park Place', 'Tuttle Park', 'Columbus', 'OSU Sustainability Institute', False, 'Free', + 'The weekly Ohio State Farmers Market features fresh produce, artisan foods, and ' + 'handcrafted goods from local farmers and makers. Free to attend, cash and card accepted ' + 'at most vendors. Open to the campus community and Columbus neighbors.'), + ('Graduate Admissions Open House — College of Engineering', 'Lecture', + future + timedelta(days=15), future + timedelta(days=15, hours=3), + 'Dreese Laboratory Atrium', 'Dreese Lab', 'Columbus', 'Graduate School', True, 'Free', + 'Prospective graduate students in engineering are invited to learn about MS and PhD programs, ' + 'research opportunities, fellowship funding, and campus life at Ohio State. Department faculty ' + 'and current graduate students will be available for Q&A.'), + ('Infectious Disease Grand Rounds: Lessons from COVID-19', 'Lecture', + future + timedelta(days=18), future + timedelta(days=18, hours=1, minutes=30), + 'Meiling Hall Auditorium', 'Meiling Hall', 'Columbus', 'Infectious Disease Institute', False, 'Free', + 'Dr. Michael Oglesbee of the Infectious Disease Institute will lead a discussion on ' + 'surveillance, vaccine distribution, and pandemic preparedness lessons drawn from the ' + 'COVID-19 pandemic response. CME credit available for health care professionals.'), + ('Ohio State Women\'s Basketball Home Opener', 'Sports', + future + timedelta(days=20), future + timedelta(days=20, hours=2), + 'Value City Arena', 'Value City Arena', 'Columbus', 'OSU Athletics', False, 'Varies', + 'Cheer on the Buckeyes as the women\'s basketball team opens their home schedule at ' + 'Value City Arena. The team is led by experienced coach Kevin McGuff and returns several ' + 'key contributors from last year\'s NCAA Tournament team. Student tickets available.'), + ('Presidential Lecture Series: Dr. Ayanna Howard on Ethical AI', 'Lecture', + future + timedelta(days=23), future + timedelta(days=23, hours=1, minutes=30), + 'Research Commons, 18th Avenue Library', 'Libraries', 'Columbus', 'Office of Academic Affairs', False, 'Free', + 'Dean of Engineering Ayanna Howard presents a public lecture on the ethical dimensions ' + 'of artificial intelligence, drawing on her research in human-machine interaction and ' + 'algorithmic bias. This event is free and open to the campus community and the public.'), + ('Fisher College of Business Startup Competition Finals', 'Career', + future + timedelta(days=26), future + timedelta(days=26, hours=4), + 'Pfahl Hall Auditorium', 'Fisher Hall', 'Columbus', 'Fisher Entrepreneurship', True, 'Free', + 'Watch as student entrepreneurs pitch their companies to a panel of investors and business ' + 'leaders in the annual Fisher Startup Competition finals. Over $50,000 in prize money ' + 'and investment opportunities at stake. Open to the public — seats are limited.'), + ('Autumn Hike at Chadwick Arboretum', 'Social', + future + timedelta(days=29), future + timedelta(days=29, hours=2), + 'Chadwick Arboretum, 2001 Fyffe Court', 'Chadwick Arboretum', 'Columbus', 'Chadwick Arboretum', False, 'Free', + 'Enjoy a guided autumn hike through the Chadwick Arboretum with naturalist educators ' + 'highlighting seasonal plant changes, migratory birds, and sustainable landscaping practices. ' + 'Free and open to all — no registration required. Dogs welcome on leash.'), + ('Ohio Supercomputer Center Research Computing Workshop', 'Lecture', + future + timedelta(days=32), future + timedelta(days=32, hours=6), + 'Baker Systems Engineering Building', 'Baker Systems', 'Columbus', 'Ohio Supercomputer Center', True, 'Free', + 'Full-day workshop covering Ohio Supercomputer Center resources, job submission, ' + 'parallel programming, and GPU computing for research. Ideal for graduate students ' + 'and postdocs new to high-performance computing. Lunch provided for registered attendees.'), + ('CFAES Annual Farm Science Review', 'Social', + future + timedelta(days=40), future + timedelta(days=40, hours=8), + 'Molly Caren Agricultural Center', 'Molly Caren Agricultural Center', 'London', + 'CFAES Extension', False, '$15', + 'The Farm Science Review is one of the nation\'s largest agricultural shows, featuring ' + 'field demonstrations of the latest farm equipment, crop technologies, and agronomy ' + 'research. Held at the 1,000-acre Molly Caren Agricultural Center near London, Ohio.'), + ('Mental Health Awareness Week: Buckeye Wellness Fair', 'Health', + future + timedelta(days=7), future + timedelta(days=7, hours=4), + 'Ohio Union Great Hall', 'Ohio Union', 'Columbus', 'Student Life Counseling and Consultation Service', False, 'Free', + 'Ohio State\'s annual Mental Health Awareness Week features a wellness fair with ' + 'information about campus counseling resources, peer support programs, mindfulness ' + 'workshops, and interactive activities promoting mental well-being. All students welcome.'), + ('Law Review Symposium: Technology, Privacy, and the Law', 'Lecture', + future + timedelta(days=35), future + timedelta(days=35, hours=8), + 'Drinko Hall Auditorium', 'Moritz College of Law', 'Columbus', 'Ohio State Law Journal', True, 'Free', + 'The Ohio State Law Journal\'s annual symposium brings together leading scholars and ' + 'practitioners to examine legal frameworks around data privacy, surveillance, ' + 'algorithmic decision-making, and digital rights. CLE credit available.'), + ('Virtual Info Session: Online MPH Program', 'Virtual', + future + timedelta(days=14), future + timedelta(days=14, hours=1), + 'Zoom (link provided upon registration)', 'Online', 'Columbus', + 'College of Public Health', True, 'Free', + 'Learn about Ohio State\'s online Master of Public Health program, including curriculum, ' + 'admissions requirements, financial aid, and student experience. Faculty and current ' + 'students will be available to answer questions. Registration required to receive Zoom link.'), + ] + + for (title, cat, start, end, location, building, campus, organizer, reg_req, cost, desc) in events: + e = Event( + title=title, + description=desc, + start_datetime=start, + end_datetime=end, + location=location, + building=building, + campus=campus, + category=cat, + organizer=organizer, + registration_required=reg_req, + cost=cost, + ) + db.session.add(e) + db.session.flush() + + # ───────────────────────────────────────────────────────────────────────── + # BENCHMARK USERS + # ───────────────────────────────────────────────────────────────────────── + if not User.query.filter_by(email='alice@osu.edu').first(): + for username, name, email, role in [ + ('alice', 'Alice Anderson', 'alice@osu.edu', 'student'), + ('bob', 'Bob Baker', 'bob@osu.edu', 'student'), + ('carol', 'Carol Chen', 'carol@osu.edu', 'faculty'), + ('dave', 'Dave Davis', 'dave@osu.edu', 'staff'), + ]: + u = User(username=username, email=email, full_name=name, role=role) + u.set_password('test1234') + db.session.add(u) + db.session.flush() + + db.session.commit() diff --git a/sites/osu/static/css/.gitkeep b/sites/osu/static/css/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/osu/static/js/.gitkeep b/sites/osu/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/osu/tasks.jsonl b/sites/osu/tasks.jsonl new file mode 100644 index 00000000..0c187235 --- /dev/null +++ b/sites/osu/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name": "Ohio State University", "id": "Ohio State University--0", "ques": "What is the name of the dean of Fisher College of Business at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--1", "ques": "How many varsity sports does Ohio State offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--2", "ques": "What conference does Ohio State athletics compete in?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--3", "ques": "Find the head coach of Ohio State's football team.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--4", "ques": "What is the annual research expenditure at Ohio State University?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--5", "ques": "In what year was The Ohio State University founded?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--6", "ques": "What is the research focus of the Translational Data Analytics Institute (TDAI)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--7", "ques": "How many undergraduate students are enrolled at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--8", "ques": "List three colleges available at Ohio State University.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--9", "ques": "What degree types are offered in the College of Engineering at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--10", "ques": "Where does the Ohio State wrestling team compete (home venue)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--11", "ques": "Who directs the Ohio Supercomputer Center?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--12", "ques": "What professional degree does Ohio State's Moritz College of Law offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--13", "ques": "Find the chair of the Department of Mathematics at Ohio State.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--14", "ques": "What is the home stadium for Ohio State football?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--15", "ques": "How many national championships has Ohio State wrestling won?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--16", "ques": "What is the application deadline for the Ohio State MBA program?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--17", "ques": "Find a news article about Ohio State's cancer research breakthrough.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--18", "ques": "Who is the director of the James Cancer Hospital and Solove Research Institute?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--19", "ques": "What is the focus of the Center for Clean Hydrogen at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} diff --git a/sites/osu/templates/404.html b/sites/osu/templates/404.html new file mode 100644 index 00000000..b47a84a4 --- /dev/null +++ b/sites/osu/templates/404.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Page Not Found — Ohio State{% endblock %} +{% block content %} +
+
404
+

Page Not Found

+

+ The page you're looking for doesn't exist or has been moved. +

+ +
+{% endblock %} diff --git a/sites/osu/templates/500.html b/sites/osu/templates/500.html new file mode 100644 index 00000000..a5ada03a --- /dev/null +++ b/sites/osu/templates/500.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% block title %}Server Error — Ohio State{% endblock %} +{% block content %} +
+
500
+

Internal Server Error

+

+ Something went wrong on our end. Please try again later. +

+ Return to Homepage +
+{% endblock %} diff --git a/sites/osu/templates/about.html b/sites/osu/templates/about.html new file mode 100644 index 00000000..881e3ae1 --- /dev/null +++ b/sites/osu/templates/about.html @@ -0,0 +1,132 @@ +{% extends "base.html" %} +{% block title %}About Ohio State{% endblock %} +{% block content %} + + + +
+
+
{{ stats.founded }}
+
Year Founded
+
+
+
{{ (stats.undergrad_count + stats.grad_count) | int }}
+
Total Enrollment
+
+
+
600K+
+
Living Alumni
+
+
+
{{ stats.acres }}
+
Acres (Columbus Campus)
+
+
+ + +
+

Our History

+
+
+

The Ohio State University was established in 1870 as the Ohio Agricultural and Mechanical College under the Morrill Land-Grant Colleges Act. Since then, it has grown to become one of the largest universities in the United States.

+

The university's flagship Columbus campus spans 1,665 acres in the heart of Ohio's capital city, with additional regional campuses in Lima, Mansfield, Marion, Newark, and Wooster.

+

As a leading public research university, Ohio State has been at the forefront of discovery, producing Nobel laureates, Rhodes Scholars, Fulbright Scholars, and countless innovators who have shaped American society and the world.

+
+
+

Timeline Highlights

+
+ {% for year, event in [ + (1870, 'Founded as Ohio Agricultural and Mechanical College'), + (1878, 'Renamed The Ohio State University'), + (1916, 'Ohio Stadium built — The Horseshoe'), + (1930, 'Named to Association of American Universities'), + (1960, 'Jesse Owens wins 4 gold medals at Berlin Olympics (OSU alum)'), + (1967, 'Wexner Medical Center opens'), + (1980, 'National Supercomputer Center established'), + (1995, 'Online education programs launched'), + (2002, 'Football National Championship'), + (2014, 'Football National Championship'), + (2024, 'Research expenditure exceeds $1.3 billion') + ] %} +
+
{{ year }}
+
{{ event }}
+
+ {% endfor %} +
+
+
+
+ + +
+

Mission & Values

+
+
+

Mission

+

The Ohio State University advances the well-being of the people of Ohio and the global community through the creation and dissemination of knowledge.

+
+
+

Vision

+

To be the model 21st-century public research university — an institution that is as innovative, resourceful and diverse as the challenges we exist to solve.

+
+
+

Core Values

+
    +
  • ◆ Excellence in education
  • +
  • ◆ Research and discovery
  • +
  • ◆ Outreach and engagement
  • +
  • ◆ Diversity and inclusion
  • +
  • ◆ Integrity and ethics
  • +
+
+
+
+ + +
+

University Leadership

+
+ {% for title, name, desc in [ + ('President', 'Dr. Ted Carter', 'The president provides executive leadership and is the chief administrative officer of the university.'), + ('Provost', 'Dr. Melissa Gilliam', 'The provost is the chief academic officer, overseeing educational programs and research.'), + ('Board of Trustees', 'John W. Zeiger, Chair', 'The Board of Trustees governs the university and ensures it fulfills its mission.') + ] %} +
+
+
{{ title }}
+
{{ name }}
+

{{ desc }}

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

Ohio State by the Numbers

+
+ {% for num, label in [ + (stats.undergrad_count | int, 'Undergraduates'), + (stats.grad_count | int, 'Graduate Students'), + (stats.faculty_count | int, 'Faculty Members'), + (stats.degree_programs, 'Degree Programs'), + (stats.national_titles, 'National Athletic Titles'), + (stats.varsity_sports, 'Varsity Sports'), + (stats.extension_offices, 'Extension Offices'), + (stats.campuses, 'Campuses') + ] %} +
+
{{ num }}
+
{{ label }}
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/sites/osu/templates/academics.html b/sites/osu/templates/academics.html new file mode 100644 index 00000000..a6c60942 --- /dev/null +++ b/sites/osu/templates/academics.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% block title %}Academics — Ohio State{% endblock %} +{% block content %} + + +
+ Ohio State is a top-20 public research university with world-class programs across arts, sciences, engineering, medicine, and more. +
+ +

Colleges & Schools

+
+ {% for college in colleges %} +
+
+

{{ college.name }}

+

{{ college.description[:140] }}{% if college.description|length > 140 %}...{% endif %}

+
+ {% if college.dean %}
Dean: {{ college.dean }}
{% endif %} +
{{ college.undergrad_count | default(0) }} undergrad • {{ college.grad_count | default(0) }} graduate
+
Founded {{ college.founded_year }}
+
+ +
+
+ {% endfor %} +
+ +
+

Academic Resources

+
+
+
{{ total_programs }}
+
Degree Programs
+
+
+
{{ total_depts }}
+
Departments
+
+
+
{{ colleges|length }}
+
Colleges
+
+
+
6
+
Campuses
+
+
+
+{% endblock %} diff --git a/sites/osu/templates/account.html b/sites/osu/templates/account.html new file mode 100644 index 00000000..b070bc77 --- /dev/null +++ b/sites/osu/templates/account.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} +{% block title %}My Account — Ohio State{% endblock %} +{% block content %} +
+

My Account

+

Welcome, {{ current_user.full_name or current_user.username }}!

+ + +
+
+
+
+ {{ (current_user.full_name or current_user.username)[0].upper() }} +
+
+
{{ current_user.full_name or current_user.username }}
+
{{ current_user.email }}
+
Member since {{ current_user.created_at.strftime('%B %Y') }}
+ {% if current_user.bio %}

{{ current_user.bio }}

{% endif %} +
+
+ {{ current_user.role | title }} +
+
+
+
+ + +

My Saved Items ({{ bookmark_details|length }})

+ {% if bookmark_details %} + {% for detail in bookmark_details %} +
+
+
+
+ {{ detail.bookmark.item_type }} + + {% if detail.bookmark.note %} +
Note: {{ detail.bookmark.note }}
+ {% endif %} +
Saved {{ detail.bookmark.created_at.strftime('%B %d, %Y') }}
+
+
+ + + +
+
+
+
+ {% endfor %} + {% else %} +
+
+

No saved items yet

+

Bookmark programs, news, faculty, events, and more to find them here.

+ +
+ {% endif %} + +
+
+ + +
+
+
+{% endblock %} diff --git a/sites/osu/templates/admissions.html b/sites/osu/templates/admissions.html new file mode 100644 index 00000000..90df0b74 --- /dev/null +++ b/sites/osu/templates/admissions.html @@ -0,0 +1,108 @@ +{% extends "base.html" %} +{% block title %}Admissions — Ohio State{% endblock %} +{% block content %} + + + +
+
+
{{ undergrad_programs }}
+
Undergraduate Programs
+
+
+
{{ grad_programs }}
+
Graduate Programs
+
+
+
{{ online_programs }}
+
Online Programs
+
+
+ + +
+

Undergraduate Admissions

+
+
+

Ohio State welcomes applications from students across Ohio, the nation, and the world. Our comprehensive admissions process evaluates academic achievement, personal character, and potential for success.

+

Application Requirements

+
    +
  • Completed online application
  • +
  • Official high school transcripts
  • +
  • ACT or SAT scores (optional for 2024-2025)
  • +
  • Letters of recommendation
  • +
  • Personal essay
  • +
  • $60 application fee
  • +
+ +
+
+

Key Dates

+ + + + + +
Early Action DeadlineNovember 1
Regular Decision DeadlineFebruary 1
Notification DateMarch – April
Enrollment Deposit DueMay 1
+
+
Middle 50% GPA: 3.6–4.0
+
Middle 50% ACT: 27–33
+
Middle 50% SAT: 1270–1490
+
+
+
+
+ + +
+

Graduate Admissions

+
+
+

Ohio State's Graduate School offers master's, doctoral, and professional degree programs in virtually every field. Applications are reviewed by individual programs.

+

General Requirements

+
    +
  • Bachelor's degree from an accredited institution
  • +
  • Minimum undergraduate GPA of 3.0
  • +
  • Official transcripts from all institutions
  • +
  • Statement of purpose
  • +
  • Letters of recommendation (typically 3)
  • +
  • GRE scores (program-specific)
  • +
  • English proficiency for international applicants
  • +
+ +
+ +
+
+ + +
+

Financial Aid & Scholarships

+

Ohio State is committed to making a world-class education accessible. Over 60% of students receive some form of financial assistance.

+ +
+{% endblock %} diff --git a/sites/osu/templates/athletics.html b/sites/osu/templates/athletics.html new file mode 100644 index 00000000..57f77805 --- /dev/null +++ b/sites/osu/templates/athletics.html @@ -0,0 +1,89 @@ +{% extends "base.html" %} +{% block title %}Buckeye Athletics — Ohio State{% endblock %} +{% block content %} + + +
+
+
{{ teams|length }}Varsity Sports
+
{{ men_teams|length }}Men's Teams
+
{{ women_teams|length }}Women's Teams
+
Big TenConference
+
+
+ +{% if men_teams %} +
+

Men's Sports

+
+ {% for team in men_teams %} +
+
+
+
+ {{ team.sport[:2].upper() }} +
+
+ +
{{ team.sport }}
+ {% if team.recent_record %}
Record: {{ team.recent_record }}
{% endif %} +
+
+ {% if team.national_titles > 0 %} +
🏆 {{ team.national_titles }} National Title{% if team.national_titles > 1 %}s{% endif %}
+ {% endif %} +
+
+ {% endfor %} +
+
+{% endif %} + +{% if women_teams %} +
+

Women's Sports

+
+ {% for team in women_teams %} +
+
+
+
+ {{ team.sport[:2].upper() }} +
+
+ +
{{ team.sport }}
+ {% if team.recent_record %}
Record: {{ team.recent_record }}
{% endif %} +
+
+ {% if team.national_titles > 0 %} +
🏆 {{ team.national_titles }} National Title{% if team.national_titles > 1 %}s{% endif %}
+ {% endif %} +
+
+ {% endfor %} +
+
+{% endif %} + +{% if coed_teams %} +
+

Co-ed Sports

+
+ {% for team in coed_teams %} +
+
+ +
{{ team.sport }}
+
+
+ {% endfor %} +
+
+{% endif %} +{% endblock %} diff --git a/sites/osu/templates/athletics_team.html b/sites/osu/templates/athletics_team.html new file mode 100644 index 00000000..4dfc915f --- /dev/null +++ b/sites/osu/templates/athletics_team.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% block title %}{{ team.name }} — Ohio State Athletics{% endblock %} +{% block content %} + + +
+
+
+
+ {{ team.sport[:2].upper() }} +
+
+

{{ team.name }}

+

{{ team.sport }} • {{ team.gender }}

+

{{ team.conference }}

+
+
+ + {% if team.national_titles > 0 %} +
+ 🏆 + {{ team.national_titles }} National Championship{% if team.national_titles > 1 %}s{% endif %} +
+ {% endif %} + +
+
+ {% if team.coach %} +
Head Coach
{{ team.coach }}
+ {% endif %} + {% if team.home_venue %} +
Home Venue
{{ team.home_venue }}
+ {% endif %} + {% if team.recent_record %} +
Recent Record
{{ team.recent_record }}
+ {% endif %} +
Conference
{{ team.conference }}
+
+
+ +
+

About {{ team.name }}

+

+ The Ohio State {{ team.name }} compete in the {{ team.conference }} conference in {{ team.sport }}. + {% if team.coach %}The team is led by Head Coach {{ team.coach }}.{% endif %} + {% if team.home_venue %}Home games are played at {{ team.home_venue }}.{% endif %} + {% if team.national_titles > 0 %}The Buckeyes have won {{ team.national_titles }} national championship{% if team.national_titles > 1 %}s{% endif %} in this sport.{% endif %} +

+
+ + {% if current_user.is_authenticated %} +
+ + + + +
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/base.html b/sites/osu/templates/base.html new file mode 100644 index 00000000..66c99e15 --- /dev/null +++ b/sites/osu/templates/base.html @@ -0,0 +1,259 @@ + + + + + + + {% block title %}The Ohio State University{% endblock %} + + {% block extra_head %}{% endblock %} + + + + +
+
+ The Ohio State University — Columbus, Ohio 43210 +
+ {% if current_user.is_authenticated %} + My Account +
+ + +
+ {% else %} + Sign In + Register + {% endif %} + Search +
+
+
+ + + + + + + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + +
+ {% block content %}{% endblock %} +
+ + + + + {% block extra_js %}{% endblock %} + + diff --git a/sites/osu/templates/department_detail.html b/sites/osu/templates/department_detail.html new file mode 100644 index 00000000..005cf047 --- /dev/null +++ b/sites/osu/templates/department_detail.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} +{% block title %}{{ dept.name }} — Ohio State{% endblock %} +{% block content %} + + +
+
+

{{ dept.name }}

+ {% if dept.college %}

{{ dept.college.name }}

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

{{ paragraph }}

+ {% endfor %} +
+
+ + {% if programs %} +
+

Degree Programs

+
+ {% for prog in programs %} +
+
+ {{ prog.degree_type }} + {% if prog.is_online %}Online{% endif %} + +
{{ prog.units }} credits • {{ prog.duration_years }} years
+
+
+ {% endfor %} +
+
+ {% endif %} + + {% if faculty_list %} +
+

Faculty

+
+ {% for member in faculty_list %} +
+
+ +
{{ member.title }}
+ {% if member.email %}{% endif %} + {% if member.research_interests %}
{{ member.research_interests[:80] }}
{% endif %} +
+
+ {% endfor %} +
+
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/departments.html b/sites/osu/templates/departments.html new file mode 100644 index 00000000..43aaa8d7 --- /dev/null +++ b/sites/osu/templates/departments.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% block title %}Departments — Ohio State{% endblock %} +{% block content %} + + +{% for college, depts in depts_by_college.items() %} +{% if depts %} +
+

+ {{ college.name }} + {% if college.dean %}Dean: {{ college.dean }}{% endif %} +

+
+ {% for dept in depts %} +
+
+

{{ dept.name }}

+ {% if dept.chair %}
Chair: {{ dept.chair }}
{% endif %} + {% if dept.location %}
📍 {{ dept.location }}
{% endif %} +

{{ dept.description[:100] }}{% if dept.description|length > 100 %}...{% endif %}

+ +
+
+ {% endfor %} +
+
+{% endif %} +{% endfor %} +{% endblock %} diff --git a/sites/osu/templates/event_detail.html b/sites/osu/templates/event_detail.html new file mode 100644 index 00000000..b0083b05 --- /dev/null +++ b/sites/osu/templates/event_detail.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} +{% block title %}{{ event.title }} — Ohio State Events{% endblock %} +{% block content %} + + +
+
+ {{ event.category }} + {% if event.registration_required %}Registration Required{% endif %} +

{{ event.title }}

+ +
+
+
+
Date & Time
+
{{ event.start_datetime.strftime('%A, %B %d, %Y') }}
+
{{ event.start_datetime.strftime('%I:%M %p') }}{% if event.end_datetime %} – {{ event.end_datetime.strftime('%I:%M %p') }}{% endif %}
+
+ {% if event.location %} +
+
Location
+
{{ event.location }}
+ {% if event.building %}
{{ event.building }}
{% endif %} +
{{ event.campus }} Campus
+
+ {% endif %} + {% if event.organizer %} +
+
Organizer
+
{{ event.organizer }}
+
+ {% endif %} +
+
Cost
+
{{ event.cost or 'Free' }}
+
+
+
+ +
+

About This Event

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

{{ paragraph }}

+ {% endfor %} +
+
+ + {% if current_user.is_authenticated %} +
+ + + + +
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/events.html b/sites/osu/templates/events.html new file mode 100644 index 00000000..a8ea0823 --- /dev/null +++ b/sites/osu/templates/events.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} +{% block title %}Events — Ohio State{% endblock %} +{% block content %} + + + +{% endblock %} diff --git a/sites/osu/templates/faculty.html b/sites/osu/templates/faculty.html new file mode 100644 index 00000000..fcde68de --- /dev/null +++ b/sites/osu/templates/faculty.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block title %}Faculty — Ohio State{% endblock %} +{% block content %} + + + +{% endblock %} diff --git a/sites/osu/templates/faculty_profile.html b/sites/osu/templates/faculty_profile.html new file mode 100644 index 00000000..2606e93f --- /dev/null +++ b/sites/osu/templates/faculty_profile.html @@ -0,0 +1,94 @@ +{% extends "base.html" %} +{% block title %}{{ member.name }} — Ohio State Faculty{% endblock %} +{% block content %} + + +
+
+
+
+ {{ member.name[0] }} +
+
+

{{ member.name }}

+

{{ member.title }}

+ {% if member.department %}

{{ member.department.name }}{% if member.department.college %}, {{ member.department.college.name }}{% endif %}

{% endif %} + {% if member.is_emeritus %}Professor Emeritus{% endif %} +
+
+ +
+
+ {% if member.email %} + + {% endif %} + {% if member.phone %} +
Phone
{{ member.phone }}
+ {% endif %} + {% if member.office %} +
Office
{{ member.office }}
+ {% endif %} +
+
+ + {% if member.research_interests %} +
+

Research Interests

+
+ {% for interest in member.research_interests.split(',') %} + {{ interest.strip() }} + {% endfor %} +
+
+ {% endif %} + + {% if member.bio %} +
+

Biography

+
+ {% for paragraph in member.bio.split('\n\n') %} +

{{ paragraph }}

+ {% endfor %} +
+
+ {% endif %} + + {% if current_user.is_authenticated %} +
+ + + + +
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/index.html b/sites/osu/templates/index.html new file mode 100644 index 00000000..32c5ca0d --- /dev/null +++ b/sites/osu/templates/index.html @@ -0,0 +1,129 @@ +{% extends "base.html" %} +{% block title %}The Ohio State University{% endblock %} +{% block content %} +
+

The Ohio State University

+

A top-20 public research university and home to over 60,000 students and world-class faculty. Go Bucks!

+ +
+ + +
+
+
{{ stats.undergrad_count | default(46820) | int }}Undergraduates
+
{{ stats.grad_count | default(14000) | int }}Graduate Students
+
{{ stats.degree_programs | default(500) }}Degree Programs
+
{{ stats.faculty_count | default(7000) | int }}Faculty
+
#{{ stats.fulbright_rank | default(1) }}Fulbright Scholars
+
${{ stats.research_expenditure | default(1.3) }}BResearch Expenditure
+
{{ stats.campuses | default(6) }}Campuses
+
+
+ + +{% if featured_news %} +
+

Latest News

+
+ {% for article in featured_news[:3] %} +
+
+
+ {{ article.category }} + {{ article.published_date.strftime('%B %d, %Y') }} +
+ +

{{ article.summary[:150] }}{% if article.summary|length > 150 %}...{% endif %}

+ +
+
+ {% endfor %} +
+ +
+{% endif %} + + +{% if upcoming_events %} +
+

Upcoming Events

+
+ {% for event in upcoming_events %} +
+
+
+
+
{{ event.start_datetime.strftime('%b') }}
+
{{ event.start_datetime.strftime('%d') }}
+
+
+ +
{{ event.start_datetime.strftime('%I:%M %p') }} • {{ event.location }}
+
{{ event.category }}
+
+
+
+
+ {% endfor %} +
+ +
+{% endif %} + + +{% if recent_research %} +
+

Research Excellence

+
+ {% for center in recent_research %} +
+
+ +

{{ center.description[:100] }}{% if center.description|length > 100 %}...{% endif %}

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

Explore Ohio State

+ +
+{% endblock %} diff --git a/sites/osu/templates/login.html b/sites/osu/templates/login.html new file mode 100644 index 00000000..3ba88fde --- /dev/null +++ b/sites/osu/templates/login.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}Sign In — Ohio State{% endblock %} +{% block content %} +
+
+
+

Sign In

+

Access your Ohio State account

+
+ +
+
+
+ {{ form.hidden_tag() }} + +
+ {{ form.email.label }} + {{ form.email(placeholder='your.name@osu.edu', autocomplete='email') }} + {% for error in form.email.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.password.label }} + {{ form.password(placeholder='Password', autocomplete='current-password') }} + {% for error in form.password.errors %} +
{{ error }}
+ {% endfor %} +
+ + +
+ +
+ Don't have an account? Register here +
+
+
+ +
+ Demo accounts: alice@osu.edu, bob@osu.edu, carol@osu.edu, dave@osu.edu — password: test1234 +
+
+{% endblock %} diff --git a/sites/osu/templates/news.html b/sites/osu/templates/news.html new file mode 100644 index 00000000..432dc2fe --- /dev/null +++ b/sites/osu/templates/news.html @@ -0,0 +1,96 @@ +{% extends "base.html" %} +{% block title %}News — Ohio State{% endblock %} +{% block content %} + + + +{% endblock %} diff --git a/sites/osu/templates/news_article.html b/sites/osu/templates/news_article.html new file mode 100644 index 00000000..5a08746a --- /dev/null +++ b/sites/osu/templates/news_article.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} +{% block title %}{{ article.title }} — Ohio State News{% endblock %} +{% block content %} + + + +{% endblock %} diff --git a/sites/osu/templates/program_detail.html b/sites/osu/templates/program_detail.html new file mode 100644 index 00000000..eb7d5561 --- /dev/null +++ b/sites/osu/templates/program_detail.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} +{% block title %}{{ program.name }} — Ohio State{% endblock %} +{% block content %} + + +
+
+
+ {{ program.degree_type }} + {% if program.is_online %}Online Available{% endif %} +
+

{{ program.name }}

+ {% if program.college %}

{{ program.college.name }}

{% endif %} + {% if program.department %}

Department of {{ program.department.name }}

{% endif %} + +
+

Program Overview

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

{{ paragraph }}

+ {% endfor %} +
+
+ + {% if program.requirements %} +
+

Requirements

+
+ {% for line in program.requirements.split('\n') %} + {% if line.strip() %}

{{ line }}

{% endif %} + {% endfor %} +
+
+ {% endif %} + + {% if related %} +
+

Related Programs

+
+ {% for rel in related %} +
+
+ {{ rel.degree_type }} + +
+
+ {% endfor %} +
+
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/programs.html b/sites/osu/templates/programs.html new file mode 100644 index 00000000..8be44d3a --- /dev/null +++ b/sites/osu/templates/programs.html @@ -0,0 +1,98 @@ +{% extends "base.html" %} +{% block title %}Degree Programs — Ohio State{% endblock %} +{% block content %} + + + +{% endblock %} diff --git a/sites/osu/templates/register.html b/sites/osu/templates/register.html new file mode 100644 index 00000000..12a2912a --- /dev/null +++ b/sites/osu/templates/register.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Register — Ohio State{% endblock %} +{% block content %} +
+
+
+

Create Account

+

Join The Ohio State University community

+
+ +
+
+
+ {{ form.hidden_tag() }} + +
+ {{ form.username.label }} + {{ form.username(placeholder='buckeyefan', autocomplete='username') }} + {% for error in form.username.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.full_name.label }} + {{ form.full_name(placeholder='Jane Doe', autocomplete='name') }} + {% for error in form.full_name.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.email.label }} + {{ form.email(placeholder='your.name@osu.edu', autocomplete='email') }} + {% for error in form.email.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.password.label }} + {{ form.password(placeholder='At least 8 characters', autocomplete='new-password') }} + {% for error in form.password.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.confirm.label }} + {{ form.confirm(placeholder='Repeat password', autocomplete='new-password') }} + {% for error in form.confirm.errors %} +
{{ error }}
+ {% endfor %} +
+ + +
+ +
+ Already have an account? Sign in +
+
+
+
+{% endblock %} diff --git a/sites/osu/templates/research.html b/sites/osu/templates/research.html new file mode 100644 index 00000000..43248699 --- /dev/null +++ b/sites/osu/templates/research.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} +{% block title %}Research — Ohio State{% endblock %} +{% block content %} + + +
+
+
$1.3BAnnual Research Expenditure
+
{{ centers|length }}Research Centers & Institutes
+
#1Fulbright Scholars Producing Institution
+
88Extension County Offices
+
+
+ +

Research Centers & Institutes

+
+ {% for center in centers %} +
+
+

{{ center.name }}

+ {% if center.college %}
{{ center.college.name }}
{% endif %} +

{{ center.description[:160] }}{% if center.description|length > 160 %}...{% endif %}

+ {% if center.focus_areas %} +
+ {% for area in center.focus_areas.split(',')[:3] %} + {{ area.strip() }} + {% endfor %} +
+ {% endif %} +
+ {% if center.director %}Director: {{ center.director }}
{% endif %} + Founded {{ center.founded_year }} +
+ +
+
+ {% endfor %} +
+ +{% if colleges %} +
+

Research by College

+
+ {% for college in colleges %} + {% if college.research_centers %} +
+

{{ college.name }}

+ {% for rc in college.research_centers %} + {{ rc.name }} + {% endfor %} +
+ {% endif %} + {% endfor %} +
+
+{% endif %} +{% endblock %} diff --git a/sites/osu/templates/research_center.html b/sites/osu/templates/research_center.html new file mode 100644 index 00000000..f4027fcd --- /dev/null +++ b/sites/osu/templates/research_center.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} +{% block title %}{{ center.name }} — Ohio State Research{% endblock %} +{% block content %} + + +
+
+

{{ center.name }}

+ {% if center.college %}

{{ center.college.name }}

{% endif %} + + {% if center.focus_areas %} +
+ {% for area in center.focus_areas.split(',') %} + {{ area.strip() }} + {% endfor %} +
+ {% endif %} + +
+

About This Center

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

{{ paragraph }}

+ {% endfor %} +
+
+ + {% if current_user.is_authenticated %} +
+ + + + +
+ {% endif %} + + {% if related %} +
+

Related Centers

+
+ {% for rel in related %} +
+
+ +

{{ rel.description[:100] }}...

+
+
+ {% endfor %} +
+
+ {% endif %} +
+ + +
+{% endblock %} diff --git a/sites/osu/templates/search.html b/sites/osu/templates/search.html new file mode 100644 index 00000000..65ba1db1 --- /dev/null +++ b/sites/osu/templates/search.html @@ -0,0 +1,141 @@ +{% extends "base.html" %} +{% block title %}Search — Ohio State{% endblock %} +{% block content %} +

Search Ohio State

+ +
+ + +
+ +{% if q %} +

+ Found {{ total }} result{% if total != 1 %}s{% endif %} for “{{ q }}” +

+ +{% if results.programs %} +
+

Degree Programs ({{ results.programs|length }})

+
+ {% for prog in results.programs %} +
+
+ {{ prog.degree_type }} + + {% if prog.college %}
{{ prog.college.name }}
{% endif %} +
+
+ {% endfor %} +
+ +
+{% endif %} + +{% if results.news %} +
+

News ({{ results.news|length }})

+ {% for article in results.news %} +
+
{{ article.category }} {{ article.published_date.strftime('%b %d, %Y') }}
+ {{ article.title }} +

{{ article.summary[:150] }}{% if article.summary|length > 150 %}...{% endif %}

+
+ {% endfor %} + +
+{% endif %} + +{% if results.faculty %} +
+

Faculty ({{ results.faculty|length }})

+
+ {% for member in results.faculty %} +
+
+ +
{{ member.title }}
+ {% if member.department %}
{{ member.department.name }}
{% endif %} +
+
+ {% endfor %} +
+ +
+{% endif %} + +{% if results.events %} +
+

Events ({{ results.events|length }})

+ {% for event in results.events %} +
+
+
+
{{ event.start_datetime.strftime('%b') }}
+
{{ event.start_datetime.strftime('%d') }}
+
+
+ {{ event.title }} +
{{ event.location }} • {{ event.category }}
+
+
+
+ {% endfor %} + +
+{% endif %} + +{% if results.research %} +
+

Research Centers ({{ results.research|length }})

+
+ {% for center in results.research %} +
+
+ +

{{ center.description[:100] }}...

+
+
+ {% endfor %} +
+
+{% endif %} + +{% if results.athletics %} +
+

Athletics ({{ results.athletics|length }})

+
+ {% for team in results.athletics %} +
+
+ +
{{ team.sport }} • {{ team.gender }}
+
+
+ {% endfor %} +
+
+{% endif %} + +{% if total == 0 %} +
+
🔍
+

No results found

+

Try different keywords or browse our site using the navigation above.

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

What are you looking for?

+

Search across programs, news, faculty, events, research, and athletics.

+ +
+{% endif %} +{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 4d690d78..6956b0fd 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -1,11 +1,11 @@ #!/bin/bash -# WebSyn startup: launch all 16 mirror sites, then exec the original CMD. +# WebSyn startup: launch all 17 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) + cambridge_dictionary coursera espn merriam_webster osu) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" @@ -17,7 +17,7 @@ for d in "${SITES[@]}"; do cp -a "/opt/WebSyn/$d/instance_seed" "/opt/WebSyn/$d/instance" done -echo "[WebSyn] Starting 16 sites on ports ${BASE_PORT}-$((BASE_PORT + 15))..." +echo "[WebSyn] Starting ${#SITES[@]} sites on ports ${BASE_PORT}-$((BASE_PORT + ${#SITES[@]} - 1))..." for i in "${!SITES[@]}"; do site="${SITES[$i]}" port=$((BASE_PORT + i)) @@ -51,8 +51,8 @@ except Exception: exit(1) ready=$((ready + 1)) fi done - echo " [${elapsed}/${max_wait}s] ${ready}/16 sites ready" - if [ $ready -eq 16 ]; then + echo " [${elapsed}/${max_wait}s] ${ready}/${#SITES[@]} sites ready" + if [ $ready -eq ${#SITES[@]} ]; then break fi done @@ -78,6 +78,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 16 site +# keeps the container alive as long as it's running. The 17 site # subprocesses are managed via /tmp/websyn_pids/.pid. exec python3 /opt/control_server.py --port 8101 From 4890c86374daea2fa29455b919b0ab55095efc3d Mon Sep 17 00:00:00 2001 From: StephenQSstarThomas Date: Wed, 15 Jul 2026 05:04:50 -0400 Subject: [PATCH 2/3] Add osu grading contract: 20 deterministic verifiers + task review Reviewer role for PR #12 (previously unreviewed). One verify_N.py per task over a shared verify_lib.py, mirroring sites/merriam_webster/verify: deterministic-first (navigation anti-shortcut + hardcoded on-site ground truth) with an anchored llm_screenshot_shows confirmation that is skipped under --no_llm. tasks.jsonl gains verifier_path + judge_rubric per row (ground truth stays out of the agent-facing file). selfcheck.py proves all 20 verifiers PASS a correct trajectory and FAIL a wrong-answer / no-navigation one (20/20). TASK_REVIEW.md records feasibility: all 20 tasks are on-site answerable; 6 are flagged as answerable-from-prior-knowledge (mitigated by the nav check) and task 1 has a 36-vs-27 varsity-sports inconsistency. --- sites/osu/tasks.jsonl | 40 ++--- sites/osu/verify/README.md | 61 +++++++ sites/osu/verify/TASK_REVIEW.md | 97 ++++++++++++ sites/osu/verify/selfcheck.py | 150 ++++++++++++++++++ sites/osu/verify/verify_0.py | 39 +++++ sites/osu/verify/verify_1.py | 39 +++++ sites/osu/verify/verify_10.py | 39 +++++ sites/osu/verify/verify_11.py | 39 +++++ sites/osu/verify/verify_12.py | 39 +++++ sites/osu/verify/verify_13.py | 39 +++++ sites/osu/verify/verify_14.py | 39 +++++ sites/osu/verify/verify_15.py | 39 +++++ sites/osu/verify/verify_16.py | 39 +++++ sites/osu/verify/verify_17.py | 39 +++++ sites/osu/verify/verify_18.py | 39 +++++ sites/osu/verify/verify_19.py | 39 +++++ sites/osu/verify/verify_2.py | 39 +++++ sites/osu/verify/verify_3.py | 39 +++++ sites/osu/verify/verify_4.py | 39 +++++ sites/osu/verify/verify_5.py | 39 +++++ sites/osu/verify/verify_6.py | 39 +++++ sites/osu/verify/verify_7.py | 39 +++++ sites/osu/verify/verify_8.py | 43 +++++ sites/osu/verify/verify_9.py | 39 +++++ sites/osu/verify/verify_lib.py | 273 ++++++++++++++++++++++++++++++++ 25 files changed, 1385 insertions(+), 20 deletions(-) create mode 100644 sites/osu/verify/README.md create mode 100644 sites/osu/verify/TASK_REVIEW.md create mode 100644 sites/osu/verify/selfcheck.py create mode 100644 sites/osu/verify/verify_0.py create mode 100644 sites/osu/verify/verify_1.py create mode 100644 sites/osu/verify/verify_10.py create mode 100644 sites/osu/verify/verify_11.py create mode 100644 sites/osu/verify/verify_12.py create mode 100644 sites/osu/verify/verify_13.py create mode 100644 sites/osu/verify/verify_14.py create mode 100644 sites/osu/verify/verify_15.py create mode 100644 sites/osu/verify/verify_16.py create mode 100644 sites/osu/verify/verify_17.py create mode 100644 sites/osu/verify/verify_18.py create mode 100644 sites/osu/verify/verify_19.py create mode 100644 sites/osu/verify/verify_2.py create mode 100644 sites/osu/verify/verify_3.py create mode 100644 sites/osu/verify/verify_4.py create mode 100644 sites/osu/verify/verify_5.py create mode 100644 sites/osu/verify/verify_6.py create mode 100644 sites/osu/verify/verify_7.py create mode 100644 sites/osu/verify/verify_8.py create mode 100644 sites/osu/verify/verify_9.py create mode 100644 sites/osu/verify/verify_lib.py diff --git a/sites/osu/tasks.jsonl b/sites/osu/tasks.jsonl index 0c187235..2771faff 100644 --- a/sites/osu/tasks.jsonl +++ b/sites/osu/tasks.jsonl @@ -1,20 +1,20 @@ -{"web_name": "Ohio State University", "id": "Ohio State University--0", "ques": "What is the name of the dean of Fisher College of Business at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--1", "ques": "How many varsity sports does Ohio State offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--2", "ques": "What conference does Ohio State athletics compete in?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--3", "ques": "Find the head coach of Ohio State's football team.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--4", "ques": "What is the annual research expenditure at Ohio State University?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--5", "ques": "In what year was The Ohio State University founded?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--6", "ques": "What is the research focus of the Translational Data Analytics Institute (TDAI)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--7", "ques": "How many undergraduate students are enrolled at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--8", "ques": "List three colleges available at Ohio State University.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--9", "ques": "What degree types are offered in the College of Engineering at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--10", "ques": "Where does the Ohio State wrestling team compete (home venue)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--11", "ques": "Who directs the Ohio Supercomputer Center?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--12", "ques": "What professional degree does Ohio State's Moritz College of Law offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--13", "ques": "Find the chair of the Department of Mathematics at Ohio State.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--14", "ques": "What is the home stadium for Ohio State football?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--15", "ques": "How many national championships has Ohio State wrestling won?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--16", "ques": "What is the application deadline for the Ohio State MBA program?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--17", "ques": "Find a news article about Ohio State's cancer research breakthrough.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--18", "ques": "Who is the director of the James Cancer Hospital and Solove Research Institute?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} -{"web_name": "Ohio State University", "id": "Ohio State University--19", "ques": "What is the focus of the Center for Clean Hydrogen at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/"} +{"web_name": "Ohio State University", "id": "Ohio State University--0", "ques": "What is the name of the dean of Fisher College of Business at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_0.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to a page that lists Ohio State's colleges and their deans (e.g. /academics). (2) The answer MUST identify the dean of the Fisher College of Business as Anil Makhija (the page renders 'Dean Anil Makhija'). (3) A non-empty answer is required. FAIL if: the Fisher dean page was never opened (answering from prior knowledge); the named dean is anyone other than Anil Makhija; the answer is empty."} +{"web_name": "Ohio State University", "id": "Ohio State University--1", "ques": "How many varsity sports does Ohio State offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_1.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the About page (/about), where the university statistics are shown. (2) The answer MUST state that Ohio State offers 36 varsity sports (the figure stated on /about). (3) Non-empty answer required. FAIL if: /about was never opened (memory recall); a number other than 36; empty answer. JUDGE NOTE: the site STATES 36 on /about even though only a subset of teams have individual pages — grade against the stated 36."} +{"web_name": "Ohio State University", "id": "Ohio State University--2", "ques": "What conference does Ohio State athletics compete in?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_2.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the athletics section (/athletics or a team page). (2) The answer MUST be the Big Ten (Conference). (3) Non-empty. FAIL if: no athletics page was opened; a different conference is named; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--3", "ques": "Find the head coach of Ohio State's football team.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_3.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the football team page (/athletics/ohio-state-buckeyes-football). (2) The answer MUST name Ryan Day as head coach. (3) Non-empty. FAIL if: the football team page was never opened; a different coach is named; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--4", "ques": "What is the annual research expenditure at Ohio State University?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_4.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to a page stating the research expenditure — the record-research-expenditures news article or /about. (2) The answer MUST be $1.3 billion. (3) Non-empty. FAIL if: no such page was opened; a different figure; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--5", "ques": "In what year was The Ohio State University founded?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_5.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to /about. (2) The answer MUST be 1870 (founding year). (3) Non-empty. FAIL if: /about was never opened (memory recall); a different year; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--6", "ques": "What is the research focus of the Translational Data Analytics Institute (TDAI)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_6.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the TDAI research center page (/research/translational-data-analytics-institute). (2) The answer MUST describe TDAI's focus — at least two of: data analytics, machine learning, health informatics, social science. (3) Non-empty. FAIL if: the TDAI page was never opened; an unrelated focus; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--7", "ques": "How many undergraduate students are enrolled at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_7.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to /about (or /academics), which shows enrollment. (2) The answer MUST be 46,820 undergraduate students. (3) Non-empty. FAIL if: no such page was opened; a different number; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--8", "ques": "List three colleges available at Ohio State University.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_8.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to a page listing the colleges (/academics or /departments). (2) The answer MUST name at least THREE distinct Ohio State colleges that actually appear on the site (e.g. Arts and Sciences, Engineering, Medicine, Nursing, Fisher College of Business). (3) Non-empty. FAIL if: no colleges page was opened; fewer than three valid colleges are named; invented colleges."} +{"web_name": "Ohio State University", "id": "Ohio State University--9", "ques": "What degree types are offered in the College of Engineering at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_9.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the programs listing filtered to Engineering (/programs?college=engineering) or the programs page. (2) The answer MUST identify the Engineering degree types — BS, MS, and PhD (at least two of these). (3) Non-empty. FAIL if: no programs page was opened; degree types unrelated to what the site lists; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--10", "ques": "Where does the Ohio State wrestling team compete (home venue)?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_10.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the wrestling team page (/athletics/ohio-state-buckeyes-wrestling). (2) The answer MUST give the home venue: Covelli Center. (3) Non-empty. FAIL if: the wrestling page was never opened; a different venue; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--11", "ques": "Who directs the Ohio Supercomputer Center?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_11.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the Ohio Supercomputer Center page (/research/ohio-supercomputer-center). (2) The answer MUST name David Bickel as director. (3) Non-empty. FAIL if: the OSC page was never opened; a different director; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--12", "ques": "What professional degree does Ohio State's Moritz College of Law offer?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_12.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to a page naming the Moritz College of Law degree (/academics or /programs/juris-doctor-jd). (2) The answer MUST be the JD (Juris Doctor). (3) Non-empty. FAIL if: no relevant page was opened; a different degree; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--13", "ques": "Find the chair of the Department of Mathematics at Ohio State.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_13.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the Mathematics department page (/departments/department-of-mathematics). (2) The answer MUST name James Cogdell as chair. (3) Non-empty. FAIL if: the math department page was never opened; a different chair; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--14", "ques": "What is the home stadium for Ohio State football?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_14.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the football team page (/athletics/ohio-state-buckeyes-football). (2) The answer MUST be Ohio Stadium (the Horseshoe). (3) Non-empty. FAIL if: the football page was never opened; a different stadium; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--15", "ques": "How many national championships has Ohio State wrestling won?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_15.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the wrestling team page (/athletics/ohio-state-buckeyes-wrestling). (2) The answer MUST be 8 national championships (the site's stated national_titles for wrestling). (3) Non-empty. FAIL if: the wrestling page was never opened; a different count; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--16", "ques": "What is the application deadline for the Ohio State MBA program?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_16.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the MBA program page (/programs/master-of-business-administration-mba). (2) The answer MUST be the application deadline: April 1. (3) Non-empty. FAIL if: the MBA program page was never opened; a different date; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--17", "ques": "Find a news article about Ohio State's cancer research breakthrough.", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_17.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST open the cancer-breakthrough news article (/news/ohio-state-researchers-develop-breakthrough-cancer-immunotherapy). (2) The answer MUST identify that article or its cancer immunotherapy (CAR-T) breakthrough. (3) Non-empty. FAIL if: that article was never opened; an unrelated article; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--18", "ques": "Who is the director of the James Cancer Hospital and Solove Research Institute?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_18.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the James Cancer Hospital and Solove Research Institute page (/research/james-cancer-hospital-and-solove-research-institute). (2) The answer MUST name William Farrar as director. (3) Non-empty. FAIL if: the James page was never opened; a different director; empty answer."} +{"web_name": "Ohio State University", "id": "Ohio State University--19", "ques": "What is the focus of the Center for Clean Hydrogen at Ohio State?", "web": "http://localhost:40016/", "upstream_url": "https://www.osu.edu/", "verifier_path": "sites/osu/verify/verify_19.py", "judge_rubric": "FACT CHECKPOINTS: (1) The trajectory MUST include a visit to the Center for Clean Hydrogen page (/research/center-for-clean-hydrogen). (2) The answer MUST describe its focus — hydrogen (energy), including at least one of fuel cells, green hydrogen, energy storage, or the hydrogen economy/production. (3) Non-empty. FAIL if: the Clean Hydrogen page was never opened; an unrelated focus; empty answer."} diff --git a/sites/osu/verify/README.md b/sites/osu/verify/README.md new file mode 100644 index 00000000..7cfe6d72 --- /dev/null +++ b/sites/osu/verify/README.md @@ -0,0 +1,61 @@ +# osu — deterministic task verifiers + +Grading contract for the 20 Ohio State University tasks in `sites/osu/tasks.jsonl`. +Same design as `sites/merriam_webster/verify/` (the canonical example): one +`verify_N.py` per task over a shared `verify_lib.py`, deterministic-first, with an +anchored LLM screenshot check as a secondary confirmation. + +## Files + +| File | Purpose | +|------|---------| +| `verify_lib.py` | Shared harness: trajectory loading, `navigated_to` anti-shortcut check, deterministic matchers (`contains_all/any`, `contains_number`, `count_present`), anchored LLM utilities (`llm_text_match`, `llm_screenshot_shows`), the `Judge` harness, and the CLI. | +| `verify_0.py` … `verify_19.py` | One verifier per task (`Ohio State University--N`). The frozen ground truth is **hardcoded here**, never in `tasks.jsonl`. | +| `selfcheck.py` | Deterministic self-test: proves every verifier PASSes a correct trajectory and FAILs a wrong-answer / no-navigation one, with zero LLM calls. | + +## What each verifier checks (deterministic first) + +1. **Navigation (anti knowledge-shortcut).** The trajectory MUST contain a step whose + URL is the on-site page that renders the fact (e.g. `/athletics/ohio-state-buckeyes-football` + for the football coach). A correct answer with no matching page visit is memory + recall → FAIL. This is the primary defense for facts that are also true of the real + OSU (Big Ten, founded 1870, Ohio Stadium). +2. **Answer.** Token containment / number match / degree-set count against the hardcoded + ground truth. Numbers ignore thousands separators (`46,820` == `46820`). +3. **Screenshot (anchored, secondary).** One `llm_screenshot_shows` call confirms the fact + is *visibly rendered*. It is anchored on the expected content (the model never supplies + knowledge) and is **fully skipped under `--no_llm`**, so a deterministic run is decided + by checks 1–2 alone. + +Output is `{task_id, pass, reason, evidence[]}` on stdout; exit 0 = PASS, 1 = FAIL. + +## Run one verifier + +Via the standard entry point (`agent_demo/eval_judge.py`, which locates the verifier from +`trajectory.verifier_path` and runs it under `uv`): + +```bash +cd agent_demo +uv run python eval_judge.py --run_dir runs/osu0 --verifier True +``` + +Directly (deterministic only, no API key needed): + +```bash +cd agent_demo +uv run python ../sites/osu/verify/verify_3.py --run_dir ../runs/osu3 --no_llm True +``` + +The LLM screenshot check uses the unified env vars `OPENAI_API_KEY`, `OPENAI_BASE_URL`, +`JUDGE_MODEL` (same as `agent.py` / `eval_judge.py`). Without `--no_llm`, configure those +or the screenshot check cannot pass. + +## Run the self-test + +```bash +python3 sites/osu/verify/selfcheck.py # 20/20 tasks must pass all 3 assertions +``` + +For each task it builds a GOLDEN (correct nav + answer → PASS), a WRONG_ANS (correct nav + +wrong answer → FAIL) and a NO_NAV (right answer, page never opened → FAIL) trajectory and +asserts the verdict. See `TASK_REVIEW.md` for the per-task feasibility assessment. diff --git a/sites/osu/verify/TASK_REVIEW.md b/sites/osu/verify/TASK_REVIEW.md new file mode 100644 index 00000000..58c00140 --- /dev/null +++ b/sites/osu/verify/TASK_REVIEW.md @@ -0,0 +1,97 @@ +# osu — task reasonableness review + +Reviewer assessment of the 20 tasks in `sites/osu/tasks.jsonl` (from PR +[aiming-lab/WebHarbor#12](https://github.com/aiming-lab/WebHarbor/pull/12), which had +no prior review). Scope: are the tasks **feasible** (solvable by navigating the site), +**not trivially answerable from an LLM's prior knowledge**, and do they have a **stable +on-site answer**? Plus site-quality issues found while writing the grading contract. + +## Method + +1. Stood the site up locally (throwaway venv, no HF assets — osu builds its DB from + `seed_data.py` at boot) and `curl`ed the page behind every task to confirm the answer + is on-site and reachable, and to capture the exact rendered string. +2. Extracted the frozen ground truth from `seed_data.py` and hardcoded it into + `verify_0.py … verify_19.py`. +3. Ran `selfcheck.py`: **20/20 tasks pass all three assertions** (a correct trajectory + PASSes; a wrong answer FAILs; a right answer with no page visit FAILs). + +## Verdict + +**All 20 tasks are feasible, on-site answerable, stable, and now gradeable** (verifier + +rubric added). None are human-in-the-loop; none have an unstable on-site answer. Six are +weak (answerable from prior knowledge — mitigated, not fixed, by the navigation +anti-shortcut check) and one has an internal inconsistency. Per the reviewer/contributor +split these are **flagged, not rewritten** (the answers still come from the site). + +## Per-task table + +| # | Question (short) | On-site page | Ground truth (site) | Answerable from memory? | +|---|---|---|---|---| +| 0 | Fisher dean | `/academics` | Anil Makhija | No — site-specific | +| 1 | # varsity sports | `/about` | **36** | Partly (real ≈ same) — ⚠ see F2 | +| 2 | Athletics conference | `/athletics` | Big Ten | **Yes** (real-world true) | +| 3 | Football head coach | `/athletics/…-football` | Ryan Day | **Yes** (currently real) | +| 4 | Research expenditure | news article / `/about` | $1.3 billion | No — site-specific figure | +| 5 | Founding year | `/about` | 1870 | **Yes** (real-world true) | +| 6 | TDAI research focus | `/research/…-tdai` | data analytics, ML, health informatics, social science | No | +| 7 | Undergrad enrollment | `/about` | 46,820 | Partly (approx real) | +| 8 | List three colleges | `/academics` | any 3 of 16 seeded | **Yes** (generic) | +| 9 | Engineering degree types | `/programs?college=engineering` | BS, MS, PhD | Partly | +| 10 | Wrestling home venue | `/athletics/…-wrestling` | Covelli Center | No — site-specific | +| 11 | OSC director | `/research/ohio-supercomputer-center` | David Bickel | No — site-specific | +| 12 | Moritz professional degree | `/academics` / `/programs/juris-doctor-jd` | JD (Juris Doctor) | **Yes** (obvious) | +| 13 | Math dept chair | `/departments/department-of-mathematics` | James Cogdell | No — site-specific | +| 14 | Football home stadium | `/athletics/…-football` | Ohio Stadium (Horseshoe) | **Yes** (real-world true) | +| 15 | Wrestling nat'l titles | `/athletics/…-wrestling` | 8 | No — site-specific figure | +| 16 | MBA application deadline | `/programs/…-mba` | April 1 | No — site-specific | +| 17 | Cancer-breakthrough news | `/news/…-cancer-immunotherapy` | the CAR-T article | No — site-specific | +| 18 | James Cancer director | `/research/james-cancer…` | William Farrar | No — site-specific | +| 19 | Clean Hydrogen focus | `/research/center-for-clean-hydrogen` | hydrogen energy, fuel cells, green hydrogen, energy storage | No | + +## Findings + +### F1 — Knowledge-shortcut tasks (severity: minor; mitigated) +T2, T3, T5, T8, T12, T14 are answerable from an LLM's prior knowledge (Big Ten; Ryan Day; +1870; "name any 3 colleges"; JD; Ohio Stadium). The **navigation anti-shortcut check** in +each verifier requires the agent to actually open the relevant page, so a memory-only +answer FAILs even when correct — but these tasks test navigation more than retrieval. +Recommendation (optional): make them site-specific by asking for a co-located +non-guessable detail (e.g. the football coach's *recent record 11-2*, the wrestling +team's *venue*, a specific college's *dean*). Left as-is per the reviewer/contributor +split. + +### F2 — Task 1 internal inconsistency (severity: major; flagged) +`/about` **states 36 varsity sports**, but only **27** teams are seeded (`/athletics`), so an +agent that counts teams answers 27. The verifier anchors to the authoritative stated +figure (36) and the rubric tells the judge to grade against 36. Recommendation for the +contributor: either seed 36 teams or reword to "How many varsity sports does the About +page state?". + +### F3 — No frozen seed DB / images in git or HF (severity: info) +Unlike every other site, osu ships **no `instance_seed/osu.db`** and **no `static/images/`**; +its DB is generated from `seed_data.py` at **image-build time** (the `RUN` step added to the +`Dockerfile`). Upside: fully hermetic, no ~2.8 GB HF pull to reproduce/verify. Caveats: +(a) determinism now rests entirely on `seed_data.py`, and the benchmark-user rows use +`created_at=datetime.utcnow` → the generated `.db` is **not byte-reproducible across +builds** (stable within one image; the `/reset` byte-identity invariant still holds because +reset copies the build-frozen file); (b) no images → lower visual fidelity (fine for these +text-lookup tasks). + +### F4 — `news_article` commits on GET (severity: minor) +`app.py` does `view_count += 1; db.session.commit()` on every `/news/` view, so the +live `instance/` DB drifts from `instance_seed/` as soon as any article is opened. It does +**not** break the post-`/reset` byte-identity check (which is measured right after the +restore copy), but it is an impurity worth noting. + +### F5 — Dead code: `sites/osu/_health.py` (severity: trivial) +`_health.py` defines `health_check(...)` that nothing imports; `app.py` has its own inline +`/_health` route. Harmless; can be removed. + +### F6 — Integration with current main (severity: was a blocker; **handled in this PR**) +PR #12 was cut from a stale base: its `websyn_start.sh` would have **dropped `booking` and +replaced `merriam_webster`**, and it claimed port **40015**, which current main assigns to +`merriam_webster`. This branch instead **adds osu as the 17th site on port 40016** (updating +`websyn_start.sh`, `control_server.py`, the `Dockerfile` `EXPOSE`/seed step, `tasks.jsonl`'s +`web` URL, and the site-count references), so it merges cleanly onto the current main with +no collision or regression. diff --git a/sites/osu/verify/selfcheck.py b/sites/osu/verify/selfcheck.py new file mode 100644 index 00000000..18db9445 --- /dev/null +++ b/sites/osu/verify/selfcheck.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""selfcheck.py — deterministic self-test for the osu verifiers. + +For every task it synthesizes three trajectories and runs the matching +verify_N.py with --no_llm (zero LLM calls), asserting the verdict: + + * GOLDEN correct on-site nav + correct answer -> PASS (exit 0) + * WRONG_ANS correct on-site nav + wrong answer -> FAIL (exit 1) + * NO_NAV correct answer but never opened the page -> FAIL (exit 1) + +GOLDEN proves a correct trajectory is accepted; WRONG_ANS proves the hardcoded +ground truth actually discriminates; NO_NAV proves the navigation anti-shortcut +fires (a right answer with no page visit is rejected as memory recall). + +Each verifier is run through `uv run` inside agent_demo/ so its simpleArgParser +dependency resolves — exactly how agent_demo/eval_judge.py invokes it. + +Usage: python3 sites/osu/verify/selfcheck.py +Exit 0 iff all 20 tasks pass all three assertions. +""" +import json +import subprocess +import sys +import tempfile +from pathlib import Path + +VERIFY_DIR = Path(__file__).resolve().parent +REPO_ROOT = VERIFY_DIR.parents[2] +AGENT_DEMO = REPO_ROOT / "agent_demo" +BASE = "http://localhost:40016" + +# Per task: nav_path (a URL that satisfies the verifier's nav check), +# good answer (should PASS), bad answer (should FAIL on the answer check). +TASKS = { + 0: ("/academics", "The dean of Fisher College of Business is Anil Makhija.", + "The dean of Fisher College of Business is John Smith."), + 1: ("/about", "Ohio State offers 36 varsity sports.", + "Ohio State offers 25 varsity sports."), + 2: ("/athletics", "Ohio State competes in the Big Ten Conference.", + "Ohio State competes in the SEC."), + 3: ("/athletics/ohio-state-buckeyes-football", "The head football coach is Ryan Day.", + "The head football coach is Urban Meyer."), + 4: ("/news/ohio-state-sets-record-for-research-expenditures-at-13-billion", + "Ohio State's annual research expenditure is $1.3 billion.", + "Ohio State's annual research expenditure is $500 million."), + 5: ("/about", "The Ohio State University was founded in 1870.", + "The Ohio State University was founded in 1900."), + 6: ("/research/translational-data-analytics-institute", + "TDAI focuses on data analytics, machine learning, health informatics, and social science.", + "TDAI focuses on marine biology and volcanology."), + 7: ("/about", "There are 46,820 undergraduate students enrolled.", + "There are 12,000 undergraduate students enrolled."), + 8: ("/academics", + "Three colleges: College of Engineering, College of Medicine, and College of Nursing.", + "Three colleges: the College of Wizardry, the School of Rock, and Hogwarts."), + 9: ("/programs?college=engineering", + "The College of Engineering offers BS, MS, and PhD degrees.", + "The College of Engineering offers only short certificates."), + 10: ("/athletics/ohio-state-buckeyes-wrestling", "Wrestling competes at the Covelli Center.", + "Wrestling competes at Value City Arena."), + 11: ("/research/ohio-supercomputer-center", + "The Ohio Supercomputer Center is directed by David Bickel.", + "The Ohio Supercomputer Center is directed by Jane Doe."), + 12: ("/programs/juris-doctor-jd", "Moritz College of Law offers the JD (Juris Doctor) degree.", + "Moritz College of Law offers a two-year associate certificate."), + 13: ("/departments/department-of-mathematics", + "The chair of the Department of Mathematics is James Cogdell.", + "The chair of the Department of Mathematics is Alan Turing."), + 14: ("/athletics/ohio-state-buckeyes-football", "The home stadium is Ohio Stadium (the Horseshoe).", + "The home stadium is Michigan Stadium."), + 15: ("/athletics/ohio-state-buckeyes-wrestling", "Ohio State wrestling has won 8 national championships.", + "Ohio State wrestling has won three national championships."), + 16: ("/programs/master-of-business-administration-mba", "The MBA application deadline is April 1.", + "The MBA application deadline is March 15."), + 17: ("/news/ohio-state-researchers-develop-breakthrough-cancer-immunotherapy", + "The article 'Ohio State Researchers Develop Breakthrough Cancer Immunotherapy' reports a cancer immunotherapy breakthrough.", + "There is an article about the football team winning a game."), + 18: ("/research/james-cancer-hospital-and-solove-research-institute", + "The James Cancer Hospital and Solove Research Institute is directed by William Farrar.", + "The James Cancer Hospital is directed by Mary Johnson."), + 19: ("/research/center-for-clean-hydrogen", + "The Center for Clean Hydrogen focuses on hydrogen energy, fuel cells, green hydrogen, and energy storage.", + "The Center for Clean Hydrogen focuses on nuclear fusion reactors."), +} + + +def make_run(tmp, task_id, nav_path, answer, include_nav): + """Write a minimal trajectory.json (no screenshots — the LLM block is skipped + under --no_llm) and return the run dir.""" + d = Path(tmp) + steps = [{"url": f"{BASE}/", "action": "navigate", + "screenshot_before": "step_000.png", "screenshot_after": "step_001.png"}] + if include_nav: + steps.append({"url": f"{BASE}{nav_path}", "action": "navigate", + "screenshot_before": "step_001.png", "screenshot_after": "step_002.png"}) + traj = { + "task_id": task_id, + "steps": steps, + "final_answer": answer, + "verifier_path": f"sites/osu/verify/verify_{task_id.split('--')[1]}.py", + } + (d / "trajectory.json").write_text(json.dumps(traj, indent=2)) + return str(d) + + +def run_verifier(idx, run_dir): + vp = f"sites/osu/verify/verify_{idx}.py" + # simpleArgParser parses booleans as value flags: `--no_llm True` (a bare + # `--no_llm` errors with "expected one argument"). + cmd = ["uv", "run", "python", str(REPO_ROOT / vp), + "--run_dir", run_dir, "--no_llm", "True"] + r = subprocess.run(cmd, cwd=str(AGENT_DEMO), capture_output=True, text=True) + return r.returncode, r.stdout, r.stderr + + +def main(): + all_ok = True + rows = [] + for idx, (nav_path, good, bad) in TASKS.items(): + tid = f"Ohio State University--{idx}" + cases = [("GOLDEN", good, True, 0), + ("WRONG_ANS", bad, True, 1), + ("NO_NAV", good, False, 1)] + results = {} + for name, answer, include_nav, expected_rc in cases: + with tempfile.TemporaryDirectory() as tmp: + run_dir = make_run(tmp, tid, nav_path, answer, include_nav) + rc, out, err = run_verifier(idx, run_dir) + ok = (rc == expected_rc) + results[name] = ok + if not ok: + all_ok = False + print(f"[MISMATCH] verify_{idx} {name}: expected rc={expected_rc} got rc={rc}") + print(" stdout:", out.strip()[:400]) + if err.strip(): + print(" stderr:", err.strip()[:300]) + verdict = "OK" if all(results.values()) else "FAIL" + rows.append((idx, verdict, results)) + print(f"verify_{idx:<2} [{verdict}] " + f"golden={'P' if results['GOLDEN'] else 'x'} " + f"wrong={'P' if results['WRONG_ANS'] else 'x'} " + f"no_nav={'P' if results['NO_NAV'] else 'x'}") + + npass = sum(1 for _, v, _ in rows if v == "OK") + print(f"\n{'='*48}\nself-check: {npass}/{len(TASKS)} tasks pass all 3 assertions") + sys.exit(0 if all_ok else 1) + + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_0.py b/sites/osu/verify/verify_0.py new file mode 100644 index 00000000..54815021 --- /dev/null +++ b/sites/osu/verify/verify_0.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--0. + +Dean of Fisher College of Business -> Anil Makhija. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /academics | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--0", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_any(t, ["/academics", "/departments"]) + j.check("nav_academics", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_fisher_dean", contains_all(fa, ["Anil", "Makhija"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/academics") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Dean Anil Makhija — dean of the Fisher College of Business", + "Who is the dean of Fisher College of Business at Ohio State?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_1.py b/sites/osu/verify/verify_1.py new file mode 100644 index 00000000..21f78e45 --- /dev/null +++ b/sites/osu/verify/verify_1.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--1. + +Number of varsity sports -> 36 (stated on the About page). + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /about | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--1", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/about") + j.check("nav_about", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_varsity_36", contains_number(fa, "36"), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/about") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "36 varsity sports", + "How many varsity sports does Ohio State offer?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_10.py b/sites/osu/verify/verify_10.py new file mode 100644 index 00000000..772bc72f --- /dev/null +++ b/sites/osu/verify/verify_10.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--10. + +Wrestling home venue -> Covelli Center. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /athletics/ohio-state-buckeyes-wrestling | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--10", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/athletics/ohio-state-buckeyes-wrestling") + j.check("nav_wrestling", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_covelli", contains_any(fa, ["Covelli Center", "Covelli"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/athletics/ohio-state-buckeyes-wrestling") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Covelli Center (wrestling home venue)", + "Where does the Ohio State wrestling team compete (home venue)?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_11.py b/sites/osu/verify/verify_11.py new file mode 100644 index 00000000..4413e803 --- /dev/null +++ b/sites/osu/verify/verify_11.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--11. + +Ohio Supercomputer Center director -> David Bickel. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /research/ohio-supercomputer-center | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--11", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/research/ohio-supercomputer-center") + j.check("nav_osc", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_bickel", contains_all(fa, ["David", "Bickel"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/research/ohio-supercomputer-center") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Director David Bickel", + "Who directs the Ohio Supercomputer Center?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_12.py b/sites/osu/verify/verify_12.py new file mode 100644 index 00000000..21b40731 --- /dev/null +++ b/sites/osu/verify/verify_12.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--12. + +Moritz professional degree -> JD (Juris Doctor). + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /academics | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--12", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_any(t, ["/academics", "/programs/juris-doctor-jd", "/programs"]) + j.check("nav_moritz", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_jd", contains_any(fa, ["Juris Doctor", "J.D.", "JD"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/academics") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "the JD (Juris Doctor) degree at Moritz College of Law", + "What professional degree does Ohio State's Moritz College of Law offer?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_13.py b/sites/osu/verify/verify_13.py new file mode 100644 index 00000000..44ddbcc6 --- /dev/null +++ b/sites/osu/verify/verify_13.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--13. + +Mathematics department chair -> James Cogdell. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /departments/department-of-mathematics | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--13", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/departments/department-of-mathematics") + j.check("nav_math_dept", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_cogdell", contains_all(fa, ["James", "Cogdell"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/departments/department-of-mathematics") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Chair James Cogdell", + "Find the chair of the Department of Mathematics at Ohio State.") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_14.py b/sites/osu/verify/verify_14.py new file mode 100644 index 00000000..afd097a7 --- /dev/null +++ b/sites/osu/verify/verify_14.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--14. + +Football home stadium -> Ohio Stadium (the Horseshoe). + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /athletics/ohio-state-buckeyes-football | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--14", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/athletics/ohio-state-buckeyes-football") + j.check("nav_football", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_ohio_stadium", contains_any(fa, ["Ohio Stadium", "Horseshoe"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/athletics/ohio-state-buckeyes-football") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Ohio Stadium (the Horseshoe)", + "What is the home stadium for Ohio State football?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_15.py b/sites/osu/verify/verify_15.py new file mode 100644 index 00000000..d9fdc8d3 --- /dev/null +++ b/sites/osu/verify/verify_15.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--15. + +Wrestling national championships -> 8. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /athletics/ohio-state-buckeyes-wrestling | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--15", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/athletics/ohio-state-buckeyes-wrestling") + j.check("nav_wrestling", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_titles_8", contains_number(fa, "8"), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/athletics/ohio-state-buckeyes-wrestling") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "8 national championships / titles", + "How many national championships has Ohio State wrestling won?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_16.py b/sites/osu/verify/verify_16.py new file mode 100644 index 00000000..4e5ad7d8 --- /dev/null +++ b/sites/osu/verify/verify_16.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--16. + +MBA application deadline -> April 1. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /programs/master-of-business-administration-mba | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--16", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/programs/master-of-business-administration-mba") + j.check("nav_mba", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_april_1", contains_all(fa, ["April 1"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/programs/master-of-business-administration-mba") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "application deadline April 1", + "What is the application deadline for the Ohio State MBA program?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_17.py b/sites/osu/verify/verify_17.py new file mode 100644 index 00000000..6df136a2 --- /dev/null +++ b/sites/osu/verify/verify_17.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--17. + +Cancer-breakthrough news article -> the CAR-T immunotherapy article. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /news/ohio-state-researchers-develop-breakthrough-cancer-immunotherapy | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--17", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/news/ohio-state-researchers-develop-breakthrough-cancer-immunotherapy") + j.check("nav_cancer_article", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_cancer_breakthrough", contains_all(fa, ["cancer"]) and contains_any(fa, ["immunotherapy", "breakthrough", "CAR-T", "Farrar"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/news/ohio-state-researchers-develop-breakthrough-cancer-immunotherapy") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "the news article 'Ohio State Researchers Develop Breakthrough Cancer Immunotherapy'", + "Find a news article about Ohio State's cancer research breakthrough.") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_18.py b/sites/osu/verify/verify_18.py new file mode 100644 index 00000000..3b8d50ff --- /dev/null +++ b/sites/osu/verify/verify_18.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--18. + +James Cancer Hospital director -> William Farrar. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /research/james-cancer-hospital-and-solove-research-institute | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--18", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/research/james-cancer-hospital-and-solove-research-institute") + j.check("nav_james", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_farrar", contains_all(fa, ["William", "Farrar"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/research/james-cancer-hospital-and-solove-research-institute") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Director William Farrar", + "Who is the director of the James Cancer Hospital and Solove Research Institute?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_19.py b/sites/osu/verify/verify_19.py new file mode 100644 index 00000000..67052936 --- /dev/null +++ b/sites/osu/verify/verify_19.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--19. + +Center for Clean Hydrogen focus -> hydrogen energy / fuel cells / green hydrogen / energy storage. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /research/center-for-clean-hydrogen | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--19", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/research/center-for-clean-hydrogen") + j.check("nav_clean_hydrogen", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_hydrogen_focus", contains_any(fa, ["hydrogen"]) and count_present(fa, ["hydrogen energy", "fuel cell", "green hydrogen", "energy storage", "hydrogen economy", "hydrogen production"]) >= 1, + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/research/center-for-clean-hydrogen") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Center for Clean Hydrogen focus: hydrogen energy, fuel cells, green hydrogen, energy storage", + "What is the focus of the Center for Clean Hydrogen at Ohio State?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_2.py b/sites/osu/verify/verify_2.py new file mode 100644 index 00000000..793f5065 --- /dev/null +++ b/sites/osu/verify/verify_2.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--2. + +Athletics conference -> Big Ten. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /athletics | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--2", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/athletics") + j.check("nav_athletics", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_big_ten", contains_any(fa, ["Big Ten", "Big-Ten", "Big 10"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/athletics") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Big Ten Conference", + "What conference does Ohio State athletics compete in?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_3.py b/sites/osu/verify/verify_3.py new file mode 100644 index 00000000..6a0299db --- /dev/null +++ b/sites/osu/verify/verify_3.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--3. + +Football head coach -> Ryan Day. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /athletics/ohio-state-buckeyes-football | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--3", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/athletics/ohio-state-buckeyes-football") + j.check("nav_football", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_coach_ryan_day", contains_all(fa, ["Ryan", "Day"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/athletics/ohio-state-buckeyes-football") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Head coach Ryan Day", + "Who is the head coach of Ohio State's football team?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_4.py b/sites/osu/verify/verify_4.py new file mode 100644 index 00000000..cdc1d1d7 --- /dev/null +++ b/sites/osu/verify/verify_4.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--4. + +Annual research expenditure -> $1.3 billion. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /news/ohio-state-sets-record-for-research-expenditures | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--4", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_any(t, ["/news/ohio-state-sets-record-for-research-expenditures", "/about"]) + j.check("nav_research_expenditure", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_1_3_billion", contains_all(fa, ["1.3", "billion"]), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/news/ohio-state-sets-record-for-research-expenditures") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "$1.3 billion in annual research expenditures", + "What is the annual research expenditure at Ohio State University?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_5.py b/sites/osu/verify/verify_5.py new file mode 100644 index 00000000..d2415bda --- /dev/null +++ b/sites/osu/verify/verify_5.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--5. + +Founding year -> 1870. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /about | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--5", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/about") + j.check("nav_about", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_founded_1870", contains_number(fa, "1870"), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/about") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "founded in 1870", + "In what year was The Ohio State University founded?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_6.py b/sites/osu/verify/verify_6.py new file mode 100644 index 00000000..c1a87f19 --- /dev/null +++ b/sites/osu/verify/verify_6.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--6. + +TDAI research focus -> data analytics / ML / health informatics / social science. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /research/translational-data-analytics-institute | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--6", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_to(t, "/research/translational-data-analytics-institute") + j.check("nav_tdai", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_tdai_focus_2plus", count_present(fa, ["data analytics", "machine learning", "health informatics", "social science"]) >= 2, + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/research/translational-data-analytics-institute") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "TDAI focus areas: data analytics, machine learning, health informatics, social science", + "What is the research focus of the Translational Data Analytics Institute (TDAI)?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_7.py b/sites/osu/verify/verify_7.py new file mode 100644 index 00000000..c718fb9f --- /dev/null +++ b/sites/osu/verify/verify_7.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--7. + +Undergraduate enrollment -> 46,820. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /about | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--7", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_any(t, ["/about", "/academics"]) + j.check("nav_about", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_undergrad_46820", contains_number(fa, "46820"), + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/about") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "46,820 undergraduate students", + "How many undergraduate students are enrolled at Ohio State?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_8.py b/sites/osu/verify/verify_8.py new file mode 100644 index 00000000..c3162dd6 --- /dev/null +++ b/sites/osu/verify/verify_8.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--8. + +List three colleges -> any 3 of the 16 seeded colleges. + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /academics | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--8", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + COLLEGES = ["arts and sciences", "fisher", "human ecology", "engineering", + "agricultural", "moritz", "college of medicine", "nursing", + "optometry", "pharmacy", "public health", "social work", + "veterinary", "public affairs", "dentistry", "graduate school"] + nav_ok = navigated_any(t, ["/academics", "/departments"]) + j.check("nav_academics", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_three_colleges", count_present(fa, COLLEGES) >= 3, + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/academics") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "a list of Ohio State colleges (e.g. Arts and Sciences, Engineering, Medicine, Nursing, Fisher)", + "List three colleges available at Ohio State University.") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_9.py b/sites/osu/verify/verify_9.py new file mode 100644 index 00000000..32009219 --- /dev/null +++ b/sites/osu/verify/verify_9.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Deterministic verifier for OSU task Ohio State University--9. + +Engineering degree types -> BS, MS, PhD (at least two required). + +Checks (deterministic first; the LLM screenshot check is an anchored confirmation +that is fully skipped under --no_llm): + nav /programs | answer (ground truth hardcoded here) | screenshot shows the fact +Input/Output: see verify_lib.parse_args / Judge.emit. +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from verify_lib import (load_run, navigated_to, navigated_any, final_answer, + last_shot, shot_after_url, contains_all, contains_any, + contains_number, count_present, answer_equals, + llm_text_match, llm_screenshot_shows, Judge, parse_args) + +def main(): + a = parse_args() + j = Judge("Ohio State University--9", a.no_llm) + t = load_run(a.run_dir) + fa = final_answer(t) + nav_ok = navigated_any(t, ["college=engineering", "/programs"]) + j.check("nav_engineering_programs", nav_ok, f"urls_matched={nav_ok}") + j.check("answer_engineering_degrees_2plus", (contains_any(fa, ["BS", "B.S.", "bachelor"]) + contains_any(fa, ["MS", "M.S.", "master"]) + contains_any(fa, ["phd", "ph.d", "doctor", "doctoral"])) >= 2, + f"final={fa!r}") + if not a.no_llm: + s = shot_after_url(t, "/programs") or last_shot(t) + if s: + ok, ev = llm_screenshot_shows(s, + "Engineering degree types include BS, MS, and PhD", + "What degree types are offered in the College of Engineering at Ohio State?") + j.check("screenshot_confirms", ok, ev, llm=True) + else: + j.check("screenshot_confirms", False, "no screenshots in run", llm=True) + j.emit() + +if __name__ == "__main__": + main() diff --git a/sites/osu/verify/verify_lib.py b/sites/osu/verify/verify_lib.py new file mode 100644 index 00000000..2a460345 --- /dev/null +++ b/sites/osu/verify/verify_lib.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +"""verify_lib.py — shared deterministic + LLM utilities for Ohio State University +task verification. + +Structurally mirrors sites/merriam_webster/verify/verify_lib.py (same Judge +harness, same navigation anti-shortcut check, same anchored LLM utilities, same +CLI + JSON output contract). Two osu-specific differences: + * SITE = "osu"; + * a new contains_number() that ignores thousands separators, so a page that + renders 46820 and an agent that answers "46,820" still match. +The merriam_webster-specific saved-word/user helpers are dropped (all 20 osu +tasks are read-only look-ups); the generic SQLite helpers are kept so a future +stateful osu task (register / bookmark) can query the after-state DB. + +Philosophy: DETERMINISTIC FIRST. + 1. Trajectory navigation check (anti knowledge-shortcut): the agent MUST have + opened the on-site page that renders the fact; a correct answer with no + matching navigation is a memory-recall shortcut = FAIL. + 2. Answer check: token-containment / number / exact match against the frozen + ground truth HARDCODED in each verify_N.py (never in tasks.jsonl). + 3. LLM utilities (text match, screenshot-contains) are used ONLY as an + ADDITIONAL confirmation and are ALWAYS anchored on ground truth: the model + verifies *presence* of given content, it never supplies knowledge. Each + verify_N.py gates the LLM block behind `not a.no_llm`, so a deterministic + (--no_llm) run makes ZERO LLM calls and is decided by checks 1-2 alone. + +Input signature (per task): + --run_dir DIR agent trajectory dir: trajectory.json + screenshots/step_NNN.png + --initial_db PATH initial-state SQLite DB (default: fetched instance_seed from container) + --after_db PATH after-state SQLite DB (default: fetched live instance DB from container) + --container NAME docker container to fetch DBs from (default: $WH_CONTAINER or wh-review) + --no_llm skip LLM-based checks (run deterministic-only) +Output: JSON {task_id, pass, reason, evidence[]} to stdout; exit 0 on PASS, 1 on FAIL. +""" +import base64, json, os, re, sqlite3, subprocess, sys, tempfile, urllib.request +from pathlib import Path +from dataclasses import dataclass + +SITE = "osu" + +# ---------------------------------------------------------------- trajectory +def load_run(run_dir): + d = Path(run_dir) + traj = json.loads((d / "trajectory.json").read_text()) + traj["_run_dir"] = d + shots_dir = d / "screenshots" + traj["_shots"] = {p.name: p for p in sorted(shots_dir.glob("step_*.png"))} \ + if shots_dir.exists() else {} + return traj + +def step_urls(traj): + return [s.get("url", "") for s in traj.get("steps", [])] + +def navigated_to(traj, substr, times=1): + """Deterministic: at least `times` trajectory steps have a URL containing substr.""" + return sum(1 for u in step_urls(traj) if substr in u) >= times + +def navigated_any(traj, substrs): + return any(navigated_to(traj, s) for s in substrs) + +def final_answer(traj): + return (traj.get("final_answer") or "").strip() + +def _shot(traj, name): + if not name: + return None + p = traj["_shots"].get(Path(name).name) + return p if (p and p.exists()) else None + +def shot_after_url(traj, substr): + """screenshot_after path of the first step whose URL contains substr.""" + for s in traj.get("steps", []): + if substr in s.get("url", ""): + p = _shot(traj, s.get("screenshot_after")) + if p: + return p + return None + +def last_shot(traj): + for s in reversed(traj.get("steps", [])): + p = _shot(traj, s.get("screenshot_after")) or _shot(traj, s.get("screenshot_before")) + if p: + return p + shots = sorted(traj["_shots"].values()) + return shots[-1] if shots else None + +# ---------------------------------------------------------------- deterministic answer match +def norm(s): + return re.sub(r"\s+", " ", (s or "").strip()).casefold() + +def answer_equals(final, expected): + return norm(final) == norm(expected) + +def contains_all(final, tokens): + f = norm(final) + return all(norm(t) in f for t in tokens) + +def contains_any(final, tokens): + f = norm(final) + return any(norm(t) in f for t in tokens) + +def _strip_thousands(s): + return re.sub(r"(?<=\d),(?=\d)", "", s or "") + +def contains_number(final, number): + """True if the integer `number` appears in `final` as a standalone number, + ignoring thousands separators (so "46,820" == "46820"). Digit boundaries are + enforced so 8 does not match inside 18 or 80, and 1870 does not match 18700.""" + f = _strip_thousands(norm(final)) + n = _strip_thousands(str(number).strip()) + return re.search(rf"(? temp file.""" + src = f"{container}:/opt/WebSyn/{SITE}/{kind}/{SITE}.db" + fd, path = tempfile.mkstemp(suffix=".db") + os.close(fd) + r = subprocess.run(["docker", "cp", src, path], capture_output=True, text=True) + if r.returncode != 0: + try: + os.unlink(path) + except OSError: + pass + raise RuntimeError(f"docker cp {src} failed: {r.stderr.strip()}") + return path + +def resolve_db(arg, container, kind): + if arg: + return arg + try: + return fetch_db(container, kind) + except Exception: + return None # caller treats None as "unavailable" and FAILs that check + +def db_query(db_path, sql, params=()): + con = sqlite3.connect(db_path) + try: + return con.execute(sql, params).fetchall() + finally: + con.close() + +# ---------------------------------------------------------------- shared LLM utilities (anchored) +# Unified LLM config, same env vars as agent.py / eval_judge.py: +# OPENAI_API_KEY, OPENAI_BASE_URL, JUDGE_MODEL +import simpleArgParser as sap + +# When --no_llm is set (via Judge), the llm_* helpers short-circuit so verifiers +# that call them directly still make ZERO LLM calls. +_NO_LLM = False + + +def _llm_config(): + """Resolve (api_key, api_base, model) from env once per process.""" + key = os.environ.get("OPENAI_API_KEY", "") + base = os.environ.get("OPENAI_BASE_URL", "") + model = os.environ.get("JUDGE_MODEL", "") + return key, base, model + + +def _chat(messages, max_tokens=1024): + """One LLM call against the configured OpenAI-compatible endpoint. Returns text or None.""" + if _NO_LLM: + return None + key, base, model = _llm_config() + if not (key and base and model): + return None # no LLM configured -> callers treat as non-PASS + payload = {"model": model, "messages": messages, + "max_tokens": max_tokens, "temperature": 1.0} + req = urllib.request.Request(base, + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json", + "Authorization": f"Bearer {key}"}) + try: + data = json.loads(urllib.request.urlopen(req, timeout=180).read()) + except Exception: + return None # caller treats None as a non-PASS; never raises + try: + return data["choices"][0]["message"]["content"] + except Exception: + return None + +def _verdict(out): + """Normalize an LLM reply to (pass_bool, text). None/empty -> (False, '').""" + if not out: + return False, "" + s = out.strip() + return s.upper().startswith("PASS"), s + +def llm_text_match(agent_answer, ground_truth, question): + """One LLM call: does agent_answer correctly answer question AND stay consistent + with the frozen ground truth? The model is given the ground truth as an anchor + and is told NOT to use its own knowledge.""" + if _NO_LLM: + return False, "[skipped: --no_llm]" + out = _chat([{"role": "user", "content": + f"You are a STRICT binary grader.\nQuestion: {question}\n" + f"Ground-truth answer (ANCHOR — judge against THIS, never use your own knowledge): {ground_truth}\n" + f"Agent's answer: {agent_answer}\n" + f"Decide PASS or FAIL ignoring case/punctuation/word order/surrounding prose. " + f"PASS only if the agent's answer is consistent with the ground truth AND actually answers the question. " + f"Line 1: PASS or FAIL. Line 2: one-sentence reason."}]) + return _verdict(out) + +def llm_screenshot_shows(shot_path, must_show, question=""): + """One vision LLM call: does this screenshot visibly render text answering/containing + `must_show`? The model judges pixels only, anchored on the expected content.""" + if _NO_LLM: + return False, "[skipped: --no_llm]" + b64 = base64.b64encode(Path(shot_path).read_bytes()).decode() + out = _chat([{"role": "user", "content": [ + {"type": "text", "text": + f"You are a STRICT binary grader. Only what is VISIBLY rendered in this screenshot counts.\n" + f"Question the page should answer: {question}\n" + f"Expected content to verify PRESENCE of: {must_show}\n" + f"PASS only if the expected content (or a semantically equivalent on-screen answer) is visibly shown. " + f"Do NOT use prior knowledge — judge only the rendered pixels.\n" + f"Line 1: PASS or FAIL. Line 2: quote the visible evidence."}, + {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}]}]) + return _verdict(out) + +# ---------------------------------------------------------------- judge harness + CLI +class Judge: + def __init__(self, task_id, no_llm=False): + global _NO_LLM + _NO_LLM = bool(no_llm) # gate the llm_* helpers at the source + self.task_id = task_id + self.no_llm = no_llm + self.ok = True + self.reason = "" + self.evidence = [] + + def check(self, name, cond, evidence="", llm=False): + if llm and self.no_llm: + self.evidence.append(f"[SKIP] {name} (--no-llm)") + return True + if cond: + self.evidence.append(f"[PASS] {name}: {evidence}") + else: + self.ok = False + if not self.reason: + self.reason = name # record the FIRST failing check + self.evidence.append(f"[FAIL] {name}: {evidence}") + return bool(cond) + + def emit(self): + print(json.dumps({"task_id": self.task_id, "pass": self.ok, + "reason": self.reason, "evidence": self.evidence}, indent=2)) + sys.exit(0 if self.ok else 1) + +def parse_args(): + @dataclass + class VerifyArgs: + run_dir: str = "" + initial_db: str = "" + after_db: str = "" + container: str = os.environ.get("WH_CONTAINER", "wh-review") + no_llm: bool = False + + def post_process(self): + if not self.run_dir: + raise SystemExit("--run_dir is required") + return sap.parse_args(VerifyArgs) From c5b8fc7a35fa1f667074b699fdc53d34d337521d Mon Sep 17 00:00:00 2001 From: raibows Date: Mon, 7 Sep 2026 10:31:09 -0700 Subject: [PATCH 3/3] feat(osu): add source-backed official imagery --- .assets-revision | 2 +- Dockerfile | 5 +- review-reports/PR-63-FINAL-AUDIT.md | 10 +- scripts/build.sh | 4 + scripts/check_assets.sh | 5 +- scripts/extract_assets.sh | 3 + sites/osu/.requires-images | 1 + sites/osu/app.py | 71 ++++ sites/osu/fetch_images.py | 172 +++++++++ sites/osu/image_sources.json | 365 +++++++++++++++++++ sites/osu/requirements.txt | 1 + sites/osu/templates/about.html | 2 + sites/osu/templates/academics.html | 4 + sites/osu/templates/admissions.html | 2 + sites/osu/templates/athletics.html | 6 + sites/osu/templates/athletics_team.html | 2 + sites/osu/templates/base.html | 18 +- sites/osu/templates/department_detail.html | 2 + sites/osu/templates/departments.html | 2 + sites/osu/templates/event_detail.html | 2 + sites/osu/templates/events.html | 2 + sites/osu/templates/faculty.html | 2 + sites/osu/templates/faculty_profile.html | 2 + sites/osu/templates/index.html | 20 +- sites/osu/templates/news.html | 8 +- sites/osu/templates/news_article.html | 2 + sites/osu/templates/program_detail.html | 2 + sites/osu/templates/programs.html | 2 + sites/osu/templates/research.html | 4 + sites/osu/templates/research_center.html | 2 + sites/osu/verify/README.md | 6 +- sites/osu/verify/test_app.py | 5 + sites/osu/verify/test_environment_quality.py | 10 +- 33 files changed, 727 insertions(+), 19 deletions(-) create mode 100644 sites/osu/.requires-images create mode 100644 sites/osu/fetch_images.py create mode 100644 sites/osu/image_sources.json diff --git a/.assets-revision b/.assets-revision index 29bb8fc6..a25e5b8b 100644 --- a/.assets-revision +++ b/.assets-revision @@ -5,4 +5,4 @@ # is a git revision (branch name like `main`, a tag, or a specific commit # sha). Override at runtime with the ASSETS_REVISION env var. repo: ChilleD/WebHarbor -revision: 480c892e976bada6c0ea3f5a66e2b9efda65525d +revision: db9c73e62d853ed91f6152b5b7105571502b7e07 diff --git a/Dockerfile b/Dockerfile index 6a354b49..64b55fa6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,8 +41,9 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -# OSU has no external asset bundle. Materialize its tracked deterministic seed -# so boot and control-plane resets restore the same database. +# OSU's real-site image bundle is required, while its database is generated +# deterministically from tracked source data. +RUN test -n "$(ls -A /opt/WebSyn/osu/static/images)" RUN cd /opt/WebSyn/osu && python3 -c "\ import app; \ import os, shutil; \ diff --git a/review-reports/PR-63-FINAL-AUDIT.md b/review-reports/PR-63-FINAL-AUDIT.md index 1a8b8c43..fa5635eb 100644 --- a/review-reports/PR-63-FINAL-AUDIT.md +++ b/review-reports/PR-63-FINAL-AUDIT.md @@ -11,19 +11,22 @@ Seven independent review contexts examined PR #63 head `4890c86374daea2fa29455b9 | Security and state | Hard-coded secret, login/bookmark open redirects, GET logout, news GET writes, unconstrained/dangling bookmarks, no bookmark uniqueness constraint, malformed session crash, weak username normalization, and unbounded notes. | Confirmed in `sites/osu/app.py`. Fixed with environment/random secret, local redirect validation, POST logout, read-only news GET, bookmark type/object validation, 500-character notes, a database unique constraint with race handling, robust user loading, lowercase usernames, request limits, session rotation, and SQLite foreign keys. | | Task and data | Task 1 conflicted with the number of seeded team rows; several tasks were answerable from prior knowledge; task 9 accepted incomplete degree types; task 17 did not ask for a precise output; many valid-answer aliases increased grading ambiguity. | Rewrote all 20 tasks around explicit visible routes, filters, multi-field facts, and comparisons. Task 1 now explicitly asks for the About-page display. Task 9 requires all three Engineering degree types. Task 17 requires exact article title and author. Multi-entity tasks bind each number/value to its entity. | | Verifier robustness | URL substring checks accepted external or wrong paths; required search/filter/topic steps were missing; negated answers and unrelated numbers could pass; `--no_llm` removed semantic enforcement; the synthetic selfcheck omitted adversarial cases. | Replaced the common verifier library and all 20 verifiers with same-origin exact path/query/order checks, visible-link transition checks, exact task IDs, non-empty output, negation-aware facts, entity-bound comparison values, and complete database equality for every read-only task. Added positive and adversarial tests for wrong task IDs, answer-only runs, external origins, database mutation, missing filters, negation, and swapped values. | -| UI and responsive behavior | Fixed two-column detail layouts overflowed on mobile; header/search/top bar could overflow; navigation lacked an overflow affordance and current-page semantics; forms lacked clear labels; focus styling was incomplete; cards with inline flex layouts could overflow. | Added responsive detail layout classes, mobile header stacking, horizontally scrollable navigation, `min-width:0` and wrapping protections, fixed grid breakpoint ordering, accessible search names, current-page `aria-current`, and focus-visible styles. Automated tests cover 320 px, 390 px, and 1440 px layouts. | -| Integration | The PR was based on a 17-site tree while current main has 20 sites; OSU collided with IKEA at `40016`; docs and asset pins were stale; clean `scripts/build.sh` would reject OSU because it intentionally has no HF seed bundle. | Merged current main locally, retained all existing sites, appended OSU as site index 20 on `40020`, exposed `40000-40020`, and updated all documentation. Added `.build-generated-seed` support to `build.sh` and `check_assets.sh`, allowing a clean source build to generate the deterministic OSU seed while all other sites use HF assets. | +| UI and responsive behavior | Fixed two-column detail layouts overflowed on mobile; header/search/top bar could overflow; navigation lacked an overflow affordance and current-page semantics; forms lacked clear labels; focus styling was incomplete; cards with inline flex layouts could overflow; the mirror contained no real photographic assets. | Added responsive detail layout classes, mobile header stacking, horizontally scrollable navigation, `min-width:0` and wrapping protections, fixed grid breakpoint ordering, accessible search names, current-page `aria-current`, and focus-visible styles. Crawled 19 photographs from official Ohio State web properties, preserved source-page URLs and hashes in `sites/osu/image_sources.json`, and integrated the photographs into home, section, card, and detail layouts. Automated tests cover 320 px, 390 px, and 1440 px layouts. | +| Integration | The PR was based on a 17-site tree while current main has 20 sites; OSU collided with IKEA at `40016`; docs and asset pins were stale; clean `scripts/build.sh` would reject OSU because it intentionally has no HF seed bundle. | Merged current main locally, retained all existing sites, appended OSU as site index 20 on `40020`, exposed `40000-40020`, and updated all documentation. Added `.build-generated-seed` support for the deterministic OSU database and `.requires-images` enforcement for its HF-hosted image bundle. The pinned HF revision now contains `osu.tar.gz`. | | Application and data model | Import-time seed recursion under `python app.py`, broad partial-state seed gate, wall-clock event filtering, nondeterministic seeded user hashes/timestamps, missing bookmark referential validation, and brittle phrase search. | Added module-safe seed resolution, partial-database failure, fixed benchmark time, stable seeded password hash and timestamps, exact bookmark validation, and token-overlap ranked global search. The same source now generates byte-identical OSU databases in repeated clean runs. | | Test evidence | The original selfcheck used fabricated trajectories only; it did not run the site, verify rendered facts, test external-origin/path spoofing, detect database mutations, validate responsive pages, or prove a current-main Docker build. | Replaced the original selfcheck with a complete unittest entry point and added HTTP, seed, integration, positive verifier, and adversarial verifier suites. Actual Playwright task trajectories, screenshots, and before/after databases were produced for all 20 tasks. A clean build without a host OSU seed and a 21-site container smoke/reset test were completed. | ## Validation - Python compilation, shell syntax, Ruff fatal/undefined-name checks, and `git diff --check`: PASS. -- OSU HTTP, seed, integration, and verifier suite: 22 tests PASS. +- OSU HTTP, seed, image provenance, integration, and verifier suite: 24 tests PASS. - Actual Playwright completion from fresh databases: 20/20 PASS. - Deterministic verifier results for those browser runs: 20/20 PASS. - Responsive checks at 320 px, 390 px, and 1440 px: 45/45 PASS with no page-level horizontal overflow. - Repeated clean OSU seed generation: byte-identical. +- Official-image provenance manifest: 19/19 assets present, non-empty, and SHA-256 verified. +- Hugging Face dataset PR #56 merged; pinned revision `db9c73e62d853ed91f6152b5b7105571502b7e07` contains `osu.tar.gz`. +- Pinned HF `osu.tar.gz` download, deterministic tar SHA-256 (`1fc684a25890262137714b56577cd8bcbe0c5a867c65da12f3d45ab7662949f7`), and extraction: PASS. - Clean Docker build with `sites/osu/instance_seed` absent from the build context: PASS. - Container health: all 21 sites alive and all 21 site roots returned HTTP 200. - `/reset/osu`: PASS; runtime and seed SHA-256 values match. @@ -34,3 +37,4 @@ Seven independent review contexts examined PR #63 head `4890c86374daea2fa29455b9 - Agent reports: `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr63-agents/reports/` - Browser trajectories, screenshots, and verifier outputs: `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr63-fixes/e2e/` - Responsive results and screenshots: `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr63-fixes/responsive/` +- Official image source pages, source URLs, dimensions, and hashes: `sites/osu/image_sources.json` diff --git a/scripts/build.sh b/scripts/build.sh index 87f582e6..1953c7a6 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,6 +10,10 @@ TAG="${1:-webharbor:dev}" # Fast probe — if any site is missing instance_seed/, run fetch_assets. need_fetch=0 for site in sites/*/; do + if [[ -f "${site}.requires-images" ]] && { [[ ! -d "${site}static/images" ]] || [[ -z $(ls -A "${site}static/images" 2>/dev/null) ]]; }; then + need_fetch=1 + break + fi if [[ -f "${site}.build-generated-seed" ]]; then continue fi diff --git a/scripts/check_assets.sh b/scripts/check_assets.sh index db747278..2ced805d 100755 --- a/scripts/check_assets.sh +++ b/scripts/check_assets.sh @@ -27,7 +27,10 @@ for site in sites/*/; do fi done for sub in "${OPTIONAL[@]}"; do - if [[ ! -d "sites/$s/$sub" ]] || [[ -z $(ls -A "sites/$s/$sub" 2>/dev/null) ]]; then + if [[ -f "sites/$s/.requires-images" && "$sub" == "static/images" ]] && { [[ ! -d "sites/$s/$sub" ]] || [[ -z $(ls -A "sites/$s/$sub" 2>/dev/null) ]]; }; then + echo " MISSING (required): sites/$s/$sub" + missing=$((missing + 1)) + elif [[ ! -d "sites/$s/$sub" ]] || [[ -z $(ls -A "sites/$s/$sub" 2>/dev/null) ]]; then warnings=$((warnings + 1)) fi done diff --git a/scripts/extract_assets.sh b/scripts/extract_assets.sh index 1dd5e8b0..14fbe3a6 100755 --- a/scripts/extract_assets.sh +++ b/scripts/extract_assets.sh @@ -35,6 +35,9 @@ for site_dir in sites/*/; do members=() for sub in "${SUBPATHS[@]}"; do + if [[ "$sub" == "instance_seed" && -f "${site_dir}.build-generated-seed" ]]; then + continue + fi [[ -e "$site_dir$sub" ]] && members+=("$site/$sub") done if [[ ${#members[@]} -eq 0 ]]; then diff --git a/sites/osu/.requires-images b/sites/osu/.requires-images new file mode 100644 index 00000000..7c9b0bb4 --- /dev/null +++ b/sites/osu/.requires-images @@ -0,0 +1 @@ +This site requires the static/images directory supplied by the pinned Hugging Face asset bundle. diff --git a/sites/osu/app.py b/sites/osu/app.py index 13643a28..d9b13f94 100644 --- a/sites/osu/app.py +++ b/sites/osu/app.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Ohio State University mirror — Flask application.""" +import json import os import re import secrets @@ -23,6 +24,8 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) SITE_PORT = 40020 BENCHMARK_NOW = datetime(2024, 10, 15, 12, 0, 0) +with open(os.path.join(BASE_DIR, 'image_sources.json'), encoding='utf-8') as image_manifest_file: + IMAGE_ASSETS = {item['file'].removesuffix('.webp'): item for item in json.load(image_manifest_file)['images']} app = Flask(__name__) app.config['SECRET_KEY'] = os.environ.get('OSU_SECRET_KEY') or secrets.token_hex(32) @@ -89,6 +92,69 @@ def ranked_search(rows, fields, query, limit=10): ranked.append((score, row.id, row)) return [row for _score, _row_id, row in sorted(ranked, key=lambda item: (-item[0], item[1]))[:limit]] + +def image_asset(key): + return IMAGE_ASSETS[key] + + +def news_image(article): + if article.slug == 'ohio-state-researchers-develop-breakthrough-cancer-immunotherapy': + return image_asset('cancer-immunotherapy') + if article.slug == 'ohio-state-sets-record-for-research-expenditures-at-13-billion': + return image_asset('research-hero') + category_images = { + 'Athletics': 'athletics-football', + 'Health': 'about-health-care', + 'Research': 'research-microelectronics', + 'Student': 'academics-graduate', + 'Faculty': 'about-education', + 'Campus Life': 'campus-life', + } + return image_asset(category_images.get(article.category, 'news-campus')) + + +def research_image(center): + center_images = { + 'translational-data-analytics-institute': 'research-hero', + 'james-cancer-hospital-and-solove-research-institute': 'james-cancer-hospital', + 'center-for-clean-hydrogen': 'research-mobility', + 'ohio-supercomputer-center': 'research-microelectronics', + } + return image_asset(center_images.get(center.slug, 'research-hero')) + + +def college_image(college): + college_images = { + 'arts-and-sciences': 'academics-undergraduate', + 'fisher-college-of-business': 'fisher-students', + 'education-and-human-ecology': 'about-education', + 'engineering': 'research-microelectronics', + 'food-agricultural-and-environmental-sciences': 'research-mobility', + 'moritz-college-of-law': 'campus-life', + 'medicine': 'about-health-care', + 'nursing': 'about-health-care', + 'optometry': 'academics-online', + 'pharmacy': 'research-hero', + 'public-health': 'about-health-care', + 'social-work': 'campus-life', + 'veterinary-medicine': 'research-hero', + 'john-glenn-college-of-public-affairs': 'home-hero', + 'dentistry': 'about-health-care', + 'graduate-school': 'academics-graduate', + } + return image_asset(college_images.get(college.slug, 'academics-undergraduate')) + + +def athletics_image(team): + team_images = { + 'ohio-state-buckeyes-football': 'athletics-football', + 'ohio-state-buckeyes-mens-basketball': 'athletics-basketball', + 'ohio-state-buckeyes-wrestling': 'athletics-wrestling', + 'ohio-state-buckeyes-fencing': 'athletics-fencing', + } + key = team_images.get(team.slug) + return image_asset(key) if key else None + # ─── Models ─────────────────────────────────────────────────────────────────── class User(db.Model, UserMixin): @@ -284,6 +350,11 @@ def inject_globals(): return { 'now': BENCHMARK_NOW, 'colleges': College.query.order_by(College.name).all(), + 'image_asset': image_asset, + 'news_image': news_image, + 'research_image': research_image, + 'college_image': college_image, + 'athletics_image': athletics_image, } # ─── Routes ─────────────────────────────────────────────────────────────────── diff --git a/sites/osu/fetch_images.py b/sites/osu/fetch_images.py new file mode 100644 index 00000000..5aec2270 --- /dev/null +++ b/sites/osu/fetch_images.py @@ -0,0 +1,172 @@ +"""Fetch and normalize the OSU mirror's photographs from documented official source pages. + +Run with: uv run --with pillow python sites/osu/fetch_images.py +""" +from __future__ import annotations + +import hashlib +import io +import json +from pathlib import Path +from urllib.request import Request, urlopen + +from PIL import Image, ImageOps + +SITE_DIR = Path(__file__).resolve().parent +WEBP = SITE_DIR / "static" / "images" +WEBP.mkdir(parents=True, exist_ok=True) +SOURCES = [ + ( + "home-hero", + "A group of student entrepreneurs sit at a table and discuss their plans.", + "https://editing.intcomm.osu.edu/sites/default/files/2026-08/entrepreneurship_homepage1.jpg", + "https://www.osu.edu/", + ), + ( + "campus-life", + "A student playing guitar on the Oval during Ohio State’s involvement fair.", + "https://editing.intcomm.osu.edu/sites/default/files/inline-images/apply_26_675.jpg", + "https://www.osu.edu/", + ), + ( + "academics-undergraduate", + "A student wearing headphones paints on a canvas.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-undergraduate-majors.jpg", + "https://www.osu.edu/academics", + ), + ( + "academics-graduate", + "Students work together in an academic setting.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-graduate-degrees.jpg", + "https://www.osu.edu/academics", + ), + ( + "academics-online", + "A student wearing headphones studies on a laptop.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-ohio-state-online.jpg", + "https://www.osu.edu/academics", + ), + ( + "research-hero", + "Ohio State research in action.", + "https://editing.intcomm.osu.edu/sites/default/files/inline-images/research-research-in-action.jpeg", + "https://www.osu.edu/research", + ), + ( + "research-mobility", + "A researcher works underneath an automobile.", + "https://editing.intcomm.osu.edu/sites/default/files/inline-images/Mobility_RI.jpg", + "https://www.osu.edu/research", + ), + ( + "research-microelectronics", + "A blue-gloved hand holds a microelectronic chip.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/inline-images/microelectronics.jpg", + "https://www.osu.edu/research", + ), + ( + "about-education", + "A faculty member talks to students in front of a chalkboard.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-education.jpg", + "https://www.osu.edu/about", + ), + ( + "about-health-care", + "A doctor smiles at a patient.", + "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-health-care.jpg", + "https://www.osu.edu/about", + ), + ( + "admissions-visit", + "Students visit the Ohio State campus.", + "https://undergrad.osu.edu/sites/UndergraduateAdmissions_22ac04/Images1/visit-campus-home.jpg", + "https://undergrad.osu.edu/", + ), + ( + "fisher-students", + "Students discuss a business case at Fisher College of Business.", + "https://s3.us-east-2.amazonaws.com/files.fisher.osu.edu/public/inline-images/Tab1_AcademicPrograms_0.jpg?VersionId=KnOdQG_NtxhHWMh2B1N8zOhDdc7wEYZx", + "https://fisher.osu.edu/", + ), + ( + "athletics-football", + "Ohio State football.", + "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F9%2F7%2F20250830_tdc_usa_065_large.jpg&width=1200&height=675&type=webp", + "https://ohiostatebuckeyes.com/", + ), + ( + "athletics-wrestling", + "Ohio State wrestler Nic Bouzakis competes at the Big Ten championships.", + "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F7%2FSZ9_5182_6dkzO.jpg&width=720&height=405&type=webp", + "https://ohiostatebuckeyes.com/sports/wrestling", + ), + ( + "athletics-basketball", + "Ohio State men’s basketball player John Mobley Jr. competes at the Big Ten tournament.", + "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F8%2F18%2FDJP04752_large.jpg&width=720&height=405&type=webp", + "https://ohiostatebuckeyes.com/sports/mens-basketball", + ), + ( + "athletics-fencing", + "Ohio State fencer Natalia Botello competes at the NCAA championships.", + "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F20%2F144A7346.JPG&width=720&height=405&type=webp", + "https://ohiostatebuckeyes.com/sports/fencing", + ), + ( + "james-cancer-hospital", + "The James Cancer Hospital and Solove Research Institute.", + "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/about/locations/james-cancer-hospital.jpg", + "https://cancer.osu.edu/for-cancer-researchers", + ), + ( + "cancer-immunotherapy", + "A physician discusses immunotherapy.", + "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/for-patients-and-caregivers/learn-about-cancers-and-treatments/specialized-treatment-clinics-and-centers/immunotherapy-management-clinic/dr-meara-discusses-immunotherapy.jpg", + "https://cancer.osu.edu/for-cancer-researchers", + ), + ( + "news-campus", + "The Ohio State University campus.", + "https://content.presspage.com/uploads/2170/800_ohiostatecampus-497663.jpg?10000", + "https://news.osu.edu/", + ), +] +manifest = [] +for name, alt, url, page in SOURCES: + req = Request( + url, + headers={"User-Agent": "Mozilla/5.0 (compatible; WebHarbor asset archival)"}, + ) + with urlopen(req, timeout=60) as r: + raw = r.read() + ctype = r.headers.get_content_type() + final = r.geturl() + if len(raw) < 5000: + raise RuntimeError((name, len(raw), ctype)) + with Image.open(io.BytesIO(raw)) as im: + im = ImageOps.exif_transpose(im).convert("RGB") + source_size = list(im.size) + im.thumbnail((1600, 1000), Image.Resampling.LANCZOS) + dest = WEBP / (name + ".webp") + im.save(dest, "WEBP", quality=84, method=6) + output_size = list(im.size) + manifest.append( + { + "file": name + ".webp", + "alt": alt, + "source_page": page, + "source_url": url, + "resolved_url": final, + "source_content_type": ctype, + "source_dimensions": source_size, + "output_dimensions": output_size, + "source_sha256": hashlib.sha256(raw).hexdigest(), + "output_sha256": hashlib.sha256(dest.read_bytes()).hexdigest(), + "output_bytes": dest.stat().st_size, + } + ) + print(name, ctype, source_size, "=>", output_size, dest.stat().st_size) +(SITE_DIR / "image_sources.json").write_text( + json.dumps({"images": manifest}, indent=2, ensure_ascii=False) + "\n" +) +print("TOTAL", sum(x["output_bytes"] for x in manifest)) diff --git a/sites/osu/image_sources.json b/sites/osu/image_sources.json new file mode 100644 index 00000000..83081069 --- /dev/null +++ b/sites/osu/image_sources.json @@ -0,0 +1,365 @@ +{ + "images": [ + { + "file": "home-hero.webp", + "alt": "A group of student entrepreneurs sit at a table and discuss their plans.", + "source_page": "https://www.osu.edu/", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/2026-08/entrepreneurship_homepage1.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/2026-08/entrepreneurship_homepage1.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1400, + 854 + ], + "output_dimensions": [ + 1400, + 854 + ], + "source_sha256": "b13eb65f21088f10e2ee98b4aed3c17f77a7415fcf29885b80ca74a3e90f84f3", + "output_sha256": "f9ceff1227c34d08ce766aea2dc50a149f15f86e7715dc2f569b1245039d31ff", + "output_bytes": 125284 + }, + { + "file": "campus-life.webp", + "alt": "A student playing guitar on the Oval during Ohio State’s involvement fair.", + "source_page": "https://www.osu.edu/", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/apply_26_675.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/apply_26_675.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "a115748a38a982a21c28cbad1b521faa6a5df2422590fb8efbd9db9007ebbe60", + "output_sha256": "ea1d8bd9cb5ef053104e5c0f149f475e673764f206fa5ca954de17b55c969d2f", + "output_bytes": 161900 + }, + { + "file": "academics-undergraduate.webp", + "alt": "A student wearing headphones paints on a canvas.", + "source_page": "https://www.osu.edu/academics", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-undergraduate-majors.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-undergraduate-majors.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1076, + 605 + ], + "output_dimensions": [ + 1076, + 605 + ], + "source_sha256": "8b6fb5b40eb95bca2371db168547d370270e6c66609a1a3f4c2f23ce2a9a6b07", + "output_sha256": "abc4b950f8e268d1f1062de3a62d93f4328d83b06d18f85c8b19664c8c580e25", + "output_bytes": 97854 + }, + { + "file": "academics-graduate.webp", + "alt": "Students work together in an academic setting.", + "source_page": "https://www.osu.edu/academics", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-graduate-degrees.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-graduate-degrees.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1198, + 674 + ], + "output_dimensions": [ + 1198, + 674 + ], + "source_sha256": "68808e0c46bb7ec8f39ba2141fd3c58461e4b60c2a7f555cfc0118a53b5b3079", + "output_sha256": "5f00d76f9fb37675306522e162fe450ad5c1e90f58c6605e0e9f17fbe345f734", + "output_bytes": 81994 + }, + { + "file": "academics-online.webp", + "alt": "A student wearing headphones studies on a laptop.", + "source_page": "https://www.osu.edu/academics", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-ohio-state-online.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/academics-ohio-state-online.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1198, + 674 + ], + "output_dimensions": [ + 1198, + 674 + ], + "source_sha256": "d9778ae7a4bad60c9baae858d00de96ae46f1209ddee80b3137e115e8dd2d23b", + "output_sha256": "f8d88b3ec04da9946ed03f7b394c7ac3fafbf3b2ed36db7d685bee13386725d7", + "output_bytes": 55402 + }, + { + "file": "research-hero.webp", + "alt": "Ohio State research in action.", + "source_page": "https://www.osu.edu/research", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/research-research-in-action.jpeg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/research-research-in-action.jpeg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 800 + ], + "output_dimensions": [ + 1200, + 800 + ], + "source_sha256": "087b3df1da8036e8f300446c83f1a46c13d62efba4e2183178f1002056698700", + "output_sha256": "0776af58b36dd31d0c5a43f70e79a0b8cb9ba6404e3e9b5ab381c6fdbac70ad2", + "output_bytes": 46190 + }, + { + "file": "research-mobility.webp", + "alt": "A researcher works underneath an automobile.", + "source_page": "https://www.osu.edu/research", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/Mobility_RI.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/inline-images/Mobility_RI.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "a0b1019a0ef964b5e9c01adee57780ae92d2e68f97db8ecd6e0c4655dfcff997", + "output_sha256": "adf5913b1b51621f4a1a673619a9941239ea066d32a455ab00ce10aa6062a950", + "output_bytes": 84668 + }, + { + "file": "research-microelectronics.webp", + "alt": "A blue-gloved hand holds a microelectronic chip.", + "source_page": "https://www.osu.edu/research", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/inline-images/microelectronics.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/inline-images/microelectronics.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "db9b1ddd6ad622965556b8ac268089d4d8c481bc007697e79fbeb8428dbc41d5", + "output_sha256": "d6248981af1acef4dedf3e13a18b540e793cc77990ea6886c4932b3cbfb5cae1", + "output_bytes": 82400 + }, + { + "file": "about-education.webp", + "alt": "A faculty member talks to students in front of a chalkboard.", + "source_page": "https://www.osu.edu/about", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-education.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-education.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "a677e3264891fd77681094eed1b24eba35c9ef22f5fa3345e76f6da45ce1b60b", + "output_sha256": "db22e975d16e33600ee371b4ce1298a01853cbd55b79069c04944fe521788a2c", + "output_bytes": 53442 + }, + { + "file": "about-health-care.webp", + "alt": "A doctor smiles at a patient.", + "source_page": "https://www.osu.edu/about", + "source_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-health-care.jpg", + "resolved_url": "https://editing.intcomm.osu.edu/sites/default/files/styles/widescreen/public/media/image/2022/07/about-health-care.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "0e851380d37ef36c87e33ec3facac2c64f97f2869d9f7a5df069c484cef062e3", + "output_sha256": "33915cd613e835bd5c1176807c9b2994c5b0e2cdff092a49b991a9be2343552e", + "output_bytes": 49000 + }, + { + "file": "admissions-visit.webp", + "alt": "Students visit the Ohio State campus.", + "source_page": "https://undergrad.osu.edu/", + "source_url": "https://undergrad.osu.edu/sites/UndergraduateAdmissions_22ac04/Images1/visit-campus-home.jpg", + "resolved_url": "https://undergrad.osu.edu/sites/UndergraduateAdmissions_22ac04/Images1/visit-campus-home.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 574, + 300 + ], + "output_dimensions": [ + 574, + 300 + ], + "source_sha256": "f10b2b38e90051ef962f96cab4db901f134271b3a9ef4dad141cf0fe8898c752", + "output_sha256": "aa6295ed5368156006e0b9b6e1c23796be0cabcc0c6528f9f31409a48b5848bb", + "output_bytes": 68174 + }, + { + "file": "fisher-students.webp", + "alt": "Students discuss a business case at Fisher College of Business.", + "source_page": "https://fisher.osu.edu/", + "source_url": "https://s3.us-east-2.amazonaws.com/files.fisher.osu.edu/public/inline-images/Tab1_AcademicPrograms_0.jpg?VersionId=KnOdQG_NtxhHWMh2B1N8zOhDdc7wEYZx", + "resolved_url": "https://s3.us-east-2.amazonaws.com/files.fisher.osu.edu/public/inline-images/Tab1_AcademicPrograms_0.jpg?VersionId=KnOdQG_NtxhHWMh2B1N8zOhDdc7wEYZx", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 345, + 230 + ], + "output_dimensions": [ + 345, + 230 + ], + "source_sha256": "9925b3aa97cd3b2666130986209144b890e74091d3b6f8a74de220f38f75f295", + "output_sha256": "b2198ffa231810c84c6b4b5a06553b6ce0e6706d68e0fbad84d43d6af671f299", + "output_bytes": 11362 + }, + { + "file": "athletics-football.webp", + "alt": "Ohio State football.", + "source_page": "https://ohiostatebuckeyes.com/", + "source_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F9%2F7%2F20250830_tdc_usa_065_large.jpg&width=1200&height=675&type=webp", + "resolved_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F9%2F7%2F20250830_tdc_usa_065_large.jpg&width=1200&height=675&type=webp", + "source_content_type": "image/webp", + "source_dimensions": [ + 1200, + 675 + ], + "output_dimensions": [ + 1200, + 675 + ], + "source_sha256": "e7a0d704aa83adcf3dce491c514adab060b623f741b1b1de507e629e74783392", + "output_sha256": "03f1bb4dec3ae10790a5c2b283e97e90083219ff0515ff628f4fbfbef583924f", + "output_bytes": 110532 + }, + { + "file": "athletics-wrestling.webp", + "alt": "Ohio State wrestler Nic Bouzakis competes at the Big Ten championships.", + "source_page": "https://ohiostatebuckeyes.com/sports/wrestling", + "source_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F7%2FSZ9_5182_6dkzO.jpg&width=720&height=405&type=webp", + "resolved_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F7%2FSZ9_5182_6dkzO.jpg&width=720&height=405&type=webp", + "source_content_type": "image/webp", + "source_dimensions": [ + 720, + 405 + ], + "output_dimensions": [ + 720, + 405 + ], + "source_sha256": "525360afe5d99ecbbf05e31cb417c74fe7537b6d65d9dd8cf91be246030ba7bb", + "output_sha256": "c15539639482319d9dda82bfb331d8ad4ec807ebb0468cff32396056b6f8af16", + "output_bytes": 32750 + }, + { + "file": "athletics-basketball.webp", + "alt": "Ohio State men’s basketball player John Mobley Jr. competes at the Big Ten tournament.", + "source_page": "https://ohiostatebuckeyes.com/sports/mens-basketball", + "source_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F8%2F18%2FDJP04752_large.jpg&width=720&height=405&type=webp", + "resolved_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F8%2F18%2FDJP04752_large.jpg&width=720&height=405&type=webp", + "source_content_type": "image/webp", + "source_dimensions": [ + 720, + 405 + ], + "output_dimensions": [ + 720, + 405 + ], + "source_sha256": "367102acc65eeec3031fc7b46e485ea77b00f17d7ac0568fc08c8750f1d303ac", + "output_sha256": "805b114fb66939b2e096099d297d37ec6d0e6d0b97c32e18dd34ab2c9de2923a", + "output_bytes": 30934 + }, + { + "file": "athletics-fencing.webp", + "alt": "Ohio State fencer Natalia Botello competes at the NCAA championships.", + "source_page": "https://ohiostatebuckeyes.com/sports/fencing", + "source_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F20%2F144A7346.JPG&width=720&height=405&type=webp", + "resolved_url": "https://images.sidearmdev.com/crop?url=https%3A%2F%2Fdxbhsrqyrr690.cloudfront.net%2Fsidearm.nextgen.sites%2Fohiostatebuckeyes.com%2Fimages%2F2026%2F3%2F20%2F144A7346.JPG&width=720&height=405&type=webp", + "source_content_type": "image/webp", + "source_dimensions": [ + 720, + 405 + ], + "output_dimensions": [ + 720, + 405 + ], + "source_sha256": "802a4d094eb1d715e3a6da324d80082fbe6978828ae829941cd20102c0e185ae", + "output_sha256": "86fa71fe903cfd99b2951485587397bfb0b7c4eb90612c47d83ea01e4bb0dee1", + "output_bytes": 22710 + }, + { + "file": "james-cancer-hospital.webp", + "alt": "The James Cancer Hospital and Solove Research Institute.", + "source_page": "https://cancer.osu.edu/for-cancer-researchers", + "source_url": "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/about/locations/james-cancer-hospital.jpg", + "resolved_url": "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/about/locations/james-cancer-hospital.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 700, + 525 + ], + "output_dimensions": [ + 700, + 525 + ], + "source_sha256": "958527d3763310c17f923d3dca83cd529c454b79903750492b60fe401f49de00", + "output_sha256": "2995744c6bb58c0cec2f7c0089cc755c253203a49c0488b765990cb377709e71", + "output_bytes": 71746 + }, + { + "file": "cancer-immunotherapy.webp", + "alt": "A physician discusses immunotherapy.", + "source_page": "https://cancer.osu.edu/for-cancer-researchers", + "source_url": "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/for-patients-and-caregivers/learn-about-cancers-and-treatments/specialized-treatment-clinics-and-centers/immunotherapy-management-clinic/dr-meara-discusses-immunotherapy.jpg", + "resolved_url": "https://cancer.osu.edu/-/media/images/cancer/website/pages-and-carousels/for-patients-and-caregivers/learn-about-cancers-and-treatments/specialized-treatment-clinics-and-centers/immunotherapy-management-clinic/dr-meara-discusses-immunotherapy.jpg", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 1200, + 900 + ], + "output_dimensions": [ + 1200, + 900 + ], + "source_sha256": "a48518463057cbd6cf3fea15e7b35c9bd57f562ea35b002fde5369b1c3a6ba3c", + "output_sha256": "c1b1a2ae3237a78d9162ed61ff64b11351efc9c9a9f3cc0d943ea0fb26500c74", + "output_bytes": 50192 + }, + { + "file": "news-campus.webp", + "alt": "The Ohio State University campus.", + "source_page": "https://news.osu.edu/", + "source_url": "https://content.presspage.com/uploads/2170/800_ohiostatecampus-497663.jpg?10000", + "resolved_url": "https://content.presspage.com/uploads/2170/800_ohiostatecampus-497663.jpg?10000", + "source_content_type": "image/jpeg", + "source_dimensions": [ + 751, + 500 + ], + "output_dimensions": [ + 751, + 500 + ], + "source_sha256": "0450530446eaa3d47bcad631b177f116fa4b0313dcad35761b947a69b81809ff", + "output_sha256": "a6c84e3d869ea111a5abef6af36fa2f8ad7c49d201226ac0ee54f2e964f795cb", + "output_bytes": 118998 + } + ] +} diff --git a/sites/osu/requirements.txt b/sites/osu/requirements.txt index dea5ddd1..acde45d9 100644 --- a/sites/osu/requirements.txt +++ b/sites/osu/requirements.txt @@ -4,3 +4,4 @@ Flask-Login Flask-WTF Flask-Bcrypt email-validator +Pillow diff --git a/sites/osu/templates/about.html b/sites/osu/templates/about.html index 881e3ae1..aff3c553 100644 --- a/sites/osu/templates/about.html +++ b/sites/osu/templates/about.html @@ -7,6 +7,8 @@

About The Ohio State University

Founded in 1870 as a land-grant institution, Ohio State has grown into one of America's great universities.

+{% set banner = image_asset('about-education') %} +
{{ banner.alt }}
diff --git a/sites/osu/templates/academics.html b/sites/osu/templates/academics.html index a6c60942..f2e045e7 100644 --- a/sites/osu/templates/academics.html +++ b/sites/osu/templates/academics.html @@ -7,6 +7,8 @@

Academic Excellence

{{ colleges|length }} colleges, {{ total_depts }} departments, {{ total_programs }} degree programs across all campuses.

+{% set banner = image_asset('academics-undergraduate') %} +
{{ banner.alt }}
Ohio State is a top-20 public research university with world-class programs across arts, sciences, engineering, medicine, and more. @@ -15,7 +17,9 @@

Academic Excellence

Colleges & Schools

{% for college in colleges %} + {% set college_photo = college_image(college) %}
+ {{ college_photo.alt }}

{{ college.name }}

{{ college.description[:140] }}{% if college.description|length > 140 %}...{% endif %}

diff --git a/sites/osu/templates/admissions.html b/sites/osu/templates/admissions.html index 90df0b74..ddb2786a 100644 --- a/sites/osu/templates/admissions.html +++ b/sites/osu/templates/admissions.html @@ -7,6 +7,8 @@

Admissions

Join a community of scholars, innovators, and leaders at The Ohio State University.

+{% set banner = image_asset('admissions-visit') %} +
{{ banner.alt }}
diff --git a/sites/osu/templates/athletics.html b/sites/osu/templates/athletics.html index ca6c3bbc..6b0ce7ba 100644 --- a/sites/osu/templates/athletics.html +++ b/sites/osu/templates/athletics.html @@ -7,6 +7,8 @@

Buckeye Athletics

{{ teams|length }} featured teams in this mirror. Big Ten Conference. Go Bucks!

+{% set banner = image_asset('athletics-football') %} +
{{ banner.alt }}
@@ -22,7 +24,9 @@

Buckeye Athletics

Men's Sports

{% for team in men_teams %} + {% set team_photo = athletics_image(team) %}
+ {% if team_photo %}{{ team_photo.alt }}{% endif %}
@@ -49,7 +53,9 @@

Men's Sports

Women's Sports

{% for team in women_teams %} + {% set team_photo = athletics_image(team) %}
+ {% if team_photo %}{{ team_photo.alt }}{% endif %}
diff --git a/sites/osu/templates/athletics_team.html b/sites/osu/templates/athletics_team.html index 50188279..b49b93ea 100644 --- a/sites/osu/templates/athletics_team.html +++ b/sites/osu/templates/athletics_team.html @@ -7,8 +7,10 @@ {{ team.name }}
+{% set team_photo = athletics_image(team) %}
+ {% if team_photo %}{{ team_photo.alt }}{% endif %}
{{ team.sport[:2].upper() }} diff --git a/sites/osu/templates/base.html b/sites/osu/templates/base.html index ffbfdd39..953cbf56 100644 --- a/sites/osu/templates/base.html +++ b/sites/osu/templates/base.html @@ -80,6 +80,11 @@ .badge-light { background: var(--gray-light); color: var(--text); border: 1px solid var(--gray-mid); } .card { background: var(--white); border: 1px solid var(--gray-mid); border-radius: 6px; overflow: hidden; } .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } + .card-image { display: block; width: 100%; height: 180px; object-fit: cover; background: var(--gray-light); } + .card-image-sm { width: 180px; min-height: 150px; object-fit: cover; flex-shrink: 0; } + .media-banner { margin: -30px -20px 30px; height: clamp(220px, 34vw, 400px); overflow: hidden; background: var(--gray-light); } + .media-banner img { display: block; width: 100%; height: 100%; object-fit: cover; } + .detail-photo { display: block; width: 100%; max-height: 460px; object-fit: cover; border-radius: 8px; margin-bottom: 24px; } .card-body { padding: 16px; } .card [style*="justify-content:space-between"] { min-width: 0; flex-wrap: wrap; gap: 10px; } .card-title { font-size: 16px; font-weight: bold; margin-bottom: 8px; line-height: 1.3; } @@ -128,9 +133,12 @@ .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; } .mt-4 { margin-top: 32px; } .mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 24px; } .p-3 { padding: 20px; } - .hero { background: linear-gradient(135deg, var(--scarlet) 0%, var(--scarlet-dark) 100%); color: var(--white); padding: 60px 20px; text-align: center; margin: -30px -20px 40px; } - .hero h1 { font-size: 42px; margin-bottom: 12px; } - .hero p { font-size: 18px; opacity: 0.9; font-family: sans-serif; max-width: 700px; margin: 0 auto 24px; } + .hero { position: relative; isolation: isolate; min-height: 440px; color: var(--white); padding: 60px 20px; text-align: center; margin: -30px -20px 40px; display: flex; align-items: center; justify-content: center; overflow: hidden; } + .hero::after { content: ''; position: absolute; inset: 0; z-index: -1; background: linear-gradient(90deg, rgba(20,20,20,.82), rgba(139,0,0,.66)); } + .hero-bg { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; } + .hero-content { position: relative; max-width: 850px; } + .hero h1 { font-size: 42px; margin-bottom: 12px; text-shadow: 0 2px 12px rgba(0,0,0,.55); } + .hero p { font-size: 18px; opacity: 0.95; font-family: sans-serif; max-width: 700px; margin: 0 auto 24px; text-shadow: 0 1px 8px rgba(0,0,0,.75); } .stats-bar { background: var(--gray-light); border-top: 3px solid var(--scarlet); padding: 20px 0; } .stats-bar .inner { max-width: 1200px; margin: 0 auto; padding: 0 20px; display: flex; justify-content: space-around; flex-wrap: wrap; gap: 20px; } .stat-item { text-align: center; font-family: sans-serif; } @@ -149,6 +157,10 @@ .header-search { width: 100%; } .header-search input { width: auto; min-width: 0; flex: 1; } .site-logo .logo-text { font-size: 18px; } + .hero { min-height: 360px; } + .hero h1 { font-size: 34px; } + .media-banner { height: 220px; } + .card-image-sm { width: 100%; height: 190px; min-height: 0; } .main-nav .inner { padding: 0 10px; } .main-nav a { padding: 10px 12px; } .page-header .inner { padding: 0 20px; } diff --git a/sites/osu/templates/department_detail.html b/sites/osu/templates/department_detail.html index 0edcd60b..627166d2 100644 --- a/sites/osu/templates/department_detail.html +++ b/sites/osu/templates/department_detail.html @@ -7,8 +7,10 @@ {{ dept.name }}
+{% set department_photo = image_asset('academics-undergraduate') %}
+ {{ department_photo.alt }}

{{ dept.name }}

{% if dept.college %}

{{ dept.college.name }}

{% endif %} diff --git a/sites/osu/templates/departments.html b/sites/osu/templates/departments.html index 43aaa8d7..8ef8cf69 100644 --- a/sites/osu/templates/departments.html +++ b/sites/osu/templates/departments.html @@ -7,6 +7,8 @@

Departments & Schools

Browse academic departments across all colleges at The Ohio State University.

+{% set banner = image_asset('academics-undergraduate') %} +
{{ banner.alt }}
{% for college, depts in depts_by_college.items() %} {% if depts %} diff --git a/sites/osu/templates/event_detail.html b/sites/osu/templates/event_detail.html index 68d2c1af..5d3f8749 100644 --- a/sites/osu/templates/event_detail.html +++ b/sites/osu/templates/event_detail.html @@ -7,8 +7,10 @@ {{ event.title[:50] }}{% if event.title|length > 50 %}...{% endif %}
+{% set event_photo = image_asset('campus-life') %}
+ {{ event_photo.alt }} {{ event.category }} {% if event.registration_required %}Registration Required{% endif %}

{{ event.title }}

diff --git a/sites/osu/templates/events.html b/sites/osu/templates/events.html index a8ea0823..c98ec377 100644 --- a/sites/osu/templates/events.html +++ b/sites/osu/templates/events.html @@ -7,6 +7,8 @@

Events at Ohio State

Lectures, performances, sports, and community events across all campuses.

+{% set banner = image_asset('campus-life') %} +
{{ banner.alt }}
+{% set banner = image_asset('about-education') %} +
{{ banner.alt }}
+{% set faculty_photo = image_asset('about-education') %}
+ {{ faculty_photo.alt }}
{{ member.name[0] }} diff --git a/sites/osu/templates/index.html b/sites/osu/templates/index.html index 32c5ca0d..fb08fb1e 100644 --- a/sites/osu/templates/index.html +++ b/sites/osu/templates/index.html @@ -1,13 +1,17 @@ {% extends "base.html" %} {% block title %}The Ohio State University{% endblock %} {% block content %} +{% set hero_image = image_asset('home-hero') %}
-

The Ohio State University

-

A top-20 public research university and home to over 60,000 students and world-class faculty. Go Bucks!

-
- Apply Now - Explore Programs - Our Research + {{ hero_image.alt }} +
+

The Ohio State University

+

A top-20 public research university and home to over 60,000 students and world-class faculty. Go Bucks!

+
@@ -30,7 +34,9 @@

The Ohio State University

Latest News

{% for article in featured_news[:3] %} + {% set article_image = news_image(article) %}
+ {{ article_image.alt }}
{{ article.category }} @@ -80,7 +86,9 @@

Upcoming Events

Research Excellence

{% for center in recent_research %} + {% set center_image = research_image(center) %}
+ {{ center_image.alt }}

{{ center.description[:100] }}{% if center.description|length > 100 %}...{% endif %}

diff --git a/sites/osu/templates/news.html b/sites/osu/templates/news.html index 432dc2fe..6ce74c92 100644 --- a/sites/osu/templates/news.html +++ b/sites/osu/templates/news.html @@ -7,6 +7,8 @@

Ohio State News

The latest stories, discoveries, and achievements from The Ohio State University.

+{% set banner = image_asset('news-campus') %} +
{{ banner.alt }}