Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions backend/api_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7871,6 +7871,10 @@ def _handle_routes(db):
f"SELECT COUNT(*) as c FROM jobs WHERE status='open' AND employer_id NOT IN ({seeded_user_subquery})",
seeded_values
).fetchone()['c']
accepting_jobs_count = db.execute(
f"SELECT COUNT(*) as c FROM jobs WHERE status IN ('open','reviewing') AND employer_id NOT IN ({seeded_user_subquery})",
seeded_values
).fetchone()['c']
completed_orders = db.execute(
f"""SELECT COUNT(*) as c FROM orders
WHERE status='completed'
Expand All @@ -7891,6 +7895,7 @@ def _handle_routes(db):
"workers_registered": workers_count,
"employers_registered": employers_count,
"open_jobs": jobs_count,
"accepting_jobs": accepting_jobs_count,
"completed_orders": completed_orders,
"total_users": total_users,
"categories": categories_count
Expand Down Expand Up @@ -11594,7 +11599,7 @@ def build_link_result(value):
j.title as job_title, j.category as job_category, j.status as job_status,
j.budget_amount, j.budget_type, j.employer_id,
wu.name as worker_name, wu.email as worker_email,
eu.name as employer_name
eu.name as employer_name, eu.email as employer_email
FROM applications a
JOIN jobs j ON j.id = a.job_id
JOIN users wu ON wu.id = a.worker_id
Expand Down Expand Up @@ -11623,13 +11628,31 @@ def build_link_result(value):
"needs_manual_review" if quality_flags else
"weak_or_incomplete"
)
employer_email = item.pop("employer_email", "")
if is_seeded_sample_email(employer_email):
item["operational_class"] = "sample"
item["actionability_reason"] = "seed_or_sample_employer"
elif item.get("status") == "pending" and item.get("job_status") in ("open", "reviewing"):
item["operational_class"] = "actionable"
item["actionability_reason"] = "pending_on_accepting_job"
else:
item["operational_class"] = "historical"
item["actionability_reason"] = (
"application_not_pending"
if item.get("status") != "pending"
else "job_not_accepting_applications"
)
applications.append(item)
actionable = [a for a in applications if a["operational_class"] == "actionable"]
summary = {
"total_recent_applications": len(applications),
"strong_candidates": sum(1 for a in applications if a["triage_status"] == "strong_candidate"),
"needs_manual_review": sum(1 for a in applications if a["triage_status"] == "needs_manual_review"),
"weak_or_incomplete": sum(1 for a in applications if a["triage_status"] == "weak_or_incomplete"),
"strong_candidates": sum(1 for a in actionable if a["triage_status"] == "strong_candidate"),
"needs_manual_review": sum(1 for a in actionable if a["triage_status"] == "needs_manual_review"),
"weak_or_incomplete": sum(1 for a in actionable if a["triage_status"] == "weak_or_incomplete"),
"pending_applications": sum(1 for a in applications if a.get("status") == "pending"),
"actionable_applications": len(actionable),
"sample_applications": sum(1 for a in applications if a["operational_class"] == "sample"),
"historical_applications": sum(1 for a in applications if a["operational_class"] == "historical"),
}
return json_response({"summary": summary, "applications": applications, "limit": limit})

Expand Down
74 changes: 71 additions & 3 deletions backend/test_deep_audit_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,10 +1732,17 @@ def test_admin_application_pipeline_surfaces_quality_triage(self):
db.execute("INSERT INTO users (id,email,password_hash,name,is_admin) VALUES (1,'admin@example.com','x','Admin',1)")
db.execute("INSERT INTO users (id,email,password_hash,name) VALUES (2,'worker@example.com','x','Worker')")
db.execute("INSERT INTO users (id,email,password_hash,name) VALUES (3,'employer@example.com','x','Employer')")
db.execute("INSERT INTO users (id,email,password_hash,name) VALUES (4,'hiring@cloudnative.dev','x','Sample Employer')")
db.execute("INSERT INTO sessions (user_id,token,expires_at) VALUES (1,?,datetime('now','+1 day'))", [token])
db.execute("INSERT INTO jobs (id,employer_id,title,description,category,budget_type,budget_amount,status) VALUES (7,3,'QA Job','Desc','testing','fixed',25,'open')")
db.execute("INSERT INTO jobs (id,employer_id,title,description,category,budget_type,budget_amount,status) VALUES (8,3,'Closed QA Job','Desc','testing','fixed',25,'hired')")
db.execute("INSERT INTO jobs (id,employer_id,title,description,category,budget_type,budget_amount,status) VALUES (9,4,'Sample QA Job','Desc','testing','fixed',25,'reviewing')")
db.execute("INSERT INTO jobs (id,employer_id,title,description,category,budget_type,budget_amount,status) VALUES (10,3,'Reviewed QA Job','Desc','testing','fixed',25,'reviewing')")
cover = "I can deliver this today with screenshots, a short issue list, and prioritized notes based on testing the signup flow on desktop and mobile."
db.execute("INSERT INTO applications (job_id,worker_id,cover_message,portfolio_url,status) VALUES (7,2,?,'https://example.com/proof','pending')", [cover])
db.execute("INSERT INTO applications (job_id,worker_id,cover_message,status) VALUES (8,2,'Historical pending application','pending')")
db.execute("INSERT INTO applications (job_id,worker_id,cover_message,status) VALUES (9,2,'Sample application','pending')")
db.execute("INSERT INTO applications (job_id,worker_id,cover_message,status) VALUES (10,2,'Accepted application','accepted')")
db.commit()
finally:
db.close()
Expand All @@ -1753,13 +1760,28 @@ def test_admin_application_pipeline_surfaces_quality_triage(self):
self.module.handle_request()
status, body = parse_cgi_output(out.getvalue())
self.assertEqual(status, 200, body)
self.assertEqual(body["summary"]["total_recent_applications"], 1)
self.assertEqual(body["summary"]["total_recent_applications"], 4)
self.assertEqual(body["summary"]["strong_candidates"], 1)
app = body["applications"][0]
self.assertEqual(body["summary"]["pending_applications"], 3)
self.assertEqual(body["summary"]["actionable_applications"], 1)
self.assertEqual(body["summary"]["sample_applications"], 1)
self.assertEqual(body["summary"]["historical_applications"], 2)
apps_by_job = {app["job_id"]: app for app in body["applications"]}
app = apps_by_job[7]
self.assertEqual(app["triage_status"], "strong_candidate")
self.assertEqual(app["operational_class"], "actionable")
self.assertEqual(app["actionability_reason"], "pending_on_accepting_job")
self.assertIn("specific_cover_message", app["quality_flags"])
self.assertIn("portfolio_or_proof_url", app["quality_flags"])
self.assertIn("deliverable_or_timing_signal", app["quality_flags"])
self.assertEqual(apps_by_job[8]["operational_class"], "historical")
self.assertEqual(apps_by_job[8]["actionability_reason"], "job_not_accepting_applications")
self.assertEqual(apps_by_job[9]["operational_class"], "sample")
self.assertEqual(apps_by_job[9]["actionability_reason"], "seed_or_sample_employer")
for returned_app in apps_by_job.values():
self.assertNotIn("employer_email", returned_app)
self.assertEqual(apps_by_job[10]["operational_class"], "historical")
self.assertEqual(apps_by_job[10]["actionability_reason"], "application_not_pending")

def test_admin_worker_activation_notifications_requires_admin(self):
self.module._request_ctx.request_method = "POST"
Expand Down Expand Up @@ -1892,6 +1914,51 @@ def test_public_jobs_default_includes_every_application_accepting_status(self):
{1: "open", 2: "reviewing"},
)

def test_public_platform_stats_distinguish_open_from_application_accepting_jobs(self):
db = self.module.get_db()
try:
db.execute(
"INSERT INTO users (id,email,password_hash,name) VALUES (1,'buyer@real-company.example','x','Real Buyer')"
)
db.execute(
"INSERT INTO users (id,email,password_hash,name) VALUES (2,'hiring@cloudnative.dev','x','Sample Employer')"
)
for job_id, employer_id, title, status in [
(1, 1, "Fresh public job", "open"),
(2, 1, "Public job under review", "reviewing"),
(3, 1, "Already hired job", "hired"),
(4, 2, "Sample reviewing job", "reviewing"),
]:
db.execute(
"""INSERT INTO jobs
(id,employer_id,title,description,category,budget_type,budget_amount,status)
VALUES (?,?,?,?,?,'fixed',25,?)""",
[job_id, employer_id, title, "Bounded work", "testing", status],
)
db.commit()
finally:
db.close()

status, response = self._request_api("GET", "/platform/stats")
self.assertEqual(status, 200, response)
self.assertEqual(response["open_jobs"], 1)
self.assertEqual(response["accepting_jobs"], 2)

generator = (REPO_ROOT / "scripts/generate-marketplace-pulse.py").read_text(encoding="utf-8")
self.assertIn("stats.get('accepting_jobs', len(jobs))", generator)
self.assertIn("Jobs Accepting Applications", generator)
self.assertNotIn("stats.get('open_jobs', len(jobs))", generator)

stats_page = (REPO_ROOT / "frontend/stats.html").read_text(encoding="utf-8")
self.assertIn("jobs accepting applications, registered users", stats_page)
self.assertNotIn("services listed, jobs open, registered users", stats_page)

homepage = (REPO_ROOT / "frontend/index.html").read_text(encoding="utf-8")
self.assertIn("Browse jobs accepting applications", homepage)
self.assertIn("Discover public services, jobs accepting applications", homepage)
self.assertNotIn("Loading open jobs", homepage)
self.assertNotIn("Discover public services, open jobs", homepage)


def test_public_homepage_visual_cleanup_invariants(self):
text = (REPO_ROOT / "frontend/index.html").read_text(encoding="utf-8", errors="ignore")
Expand Down Expand Up @@ -2137,7 +2204,8 @@ def test_growth_activation_pages_and_homepage_proof_are_discoverable(self):
"href=\"/#/post-job?template=lead_qualification\"",
],
"frontend/earn/open-paid-tasks.html": [
"Find open paid tasks you can apply to today",
"Find paid tasks you can apply to today",
"Jobs accepting applications",
"worker_open_tasks_click",
"What a strong application says",
],
Expand Down
13 changes: 12 additions & 1 deletion frontend/earn/open-paid-tasks.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,15 @@
if (event.target.closest('.lp-mobile-link')) toggleMobileMenu(false);
});
}
</script><div class="wrap"><section class="hero"><div class="eyebrow">Worker activation</div><h1>Find open paid tasks you can apply to today.</h1><p>Browse current jobs, apply with a short specific message, and keep work/payment on-platform.</p></section><section class="section"><h2>Open paid task types</h2><div class="grid"><article class="card"><div class="stat">3</div><h3>Open public jobs</h3><p>Current starter jobs include website QA, lead research, and AI-output review.</p><a class="cta" href="/#/jobs" onclick="trackGHH('worker_open_tasks_click',{source:'open_paid_tasks'})">Browse open jobs</a></article><article class="card"><h3>What a strong application says</h3><p>State the job you want, relevant experience, timing, and one example or link if available.</p></article><article class="card"><h3>Stay on platform</h3><p>Do not send private payment details or move work off-platform. Apply directly through GoHireHumans.</p></article></div></section><section class="section"><h2>Good worker categories now</h2><p><span class="pill">website testing</span><span class="pill">lead research</span><span class="pill">AI fact-checking</span><span class="pill">data cleanup</span><span class="pill">local verification</span></p><p><a class="cta secondary" href="/#/services" onclick="trackGHH('worker_list_service_click',{source:'open_paid_tasks'})">List a matching service</a></p></section><div class="footer">© 2026 GoHireHumans. Workers receive the listed payout; employers pay Stripe processing plus a 1% GoHireHumans fee where checkout is configured.</div></div></body></html>
</script><div class="wrap"><section class="hero"><div class="eyebrow">Worker activation</div><h1>Find paid tasks you can apply to today.</h1><p>Browse current jobs, apply with a short specific message, and keep work/payment on-platform.</p></section><section class="section"><h2>Current paid task opportunities</h2><div class="grid"><article class="card"><div class="stat" id="accepting-jobs-count" aria-live="polite">—</div><h3>Jobs accepting applications</h3><p>Browse the live list for current QA, research, review, verification, and other scoped work.</p><a class="cta" href="/#/jobs" onclick="trackGHH('worker_open_tasks_click',{source:'open_paid_tasks'})">Browse jobs</a></article><article class="card"><h3>What a strong application says</h3><p>State the job you want, relevant experience, timing, and one example or link if available.</p></article><article class="card"><h3>Stay on platform</h3><p>Do not send private payment details or move work off-platform. Apply directly through GoHireHumans.</p></article></div></section><section class="section"><h2>Useful worker categories</h2><p><span class="pill">website testing</span><span class="pill">lead research</span><span class="pill">AI fact-checking</span><span class="pill">data cleanup</span><span class="pill">local verification</span></p><p><a class="cta secondary" href="/#/services" onclick="trackGHH('worker_list_service_click',{source:'open_paid_tasks'})">List a matching service</a></p></section><div class="footer">© 2026 GoHireHumans. Workers receive the listed payout; employers pay Stripe processing plus a 1% GoHireHumans fee where checkout is configured.</div></div><script>
fetch('https://gohirehumans-production.up.railway.app/platform/stats')
.then(response => response.ok ? response.json() : null)
.then(stats => {
if (!stats || !Object.prototype.hasOwnProperty.call(stats, 'accepting_jobs')) return;
const count = stats.accepting_jobs;
if (typeof count === 'number' && Number.isInteger(count) && count >= 0) {
document.getElementById('accepting-jobs-count').textContent = count.toLocaleString('en-US');
}
})
.catch(() => {});
</script></body></html>
10 changes: 5 additions & 5 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
"@context": "https://schema.org",
"@type": "ItemList",
"name": "Job Categories on GoHireHumans",
"description": "Browse open jobs — humans and AI agents can find paid work here",
"description": "Browse jobs accepting applications — humans and AI agents can find paid work here",
"url": "https://www.gohirehumans.com/#/jobs",
"numberOfItems": 6,
"itemListElement": [
Expand Down Expand Up @@ -1912,7 +1912,7 @@ <h1 class="browse-title">Browse Jobs</h1>
: `<div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--color-divider)"><p style="font-size:var(--text-xs);color:var(--color-text-faint);margin-bottom:var(--space-3)">Post jobs and find great talent</p><button class="btn btn-primary" style="width:100%" onclick="navigate('#/register')">Post a job free</button></div>`}
</aside>
<div class="browse-content">
<div class="browse-summary" id="jobs-summary" aria-live="polite">Loading open jobs…</div>
<div class="browse-summary" id="jobs-summary" aria-live="polite">Loading jobs…</div>
<div id="jobs-list" class="job-list">
${Array(5).fill(0).map(()=>`<div class="skeleton-card" style="height:100px"></div>`).join('')}
</div>
Expand Down Expand Up @@ -4211,10 +4211,10 @@ <h1 class="page-title">Audit Log</h1>
<section class="lp-hero" style="padding-bottom:clamp(2rem,4vw,3rem)">
<div class="lp-hero-eyebrow">${I.bot} For AI Agents</div>
<h1>A services marketplace<br><span class="accent">agents can actually read.</span></h1>
<p style="max-width:640px;margin:0 auto">Discover public services, open jobs, and scoped work that can be routed to humans or other agents. GoHireHumans is built for agent-assisted buying and earning — with spending, publishing, and paid commitments routed through human/account-owner approval unless explicitly pre-authorized.</p>
<p style="max-width:640px;margin:0 auto">Discover public services, jobs accepting applications, and scoped work that can be routed to humans or other agents. GoHireHumans is built for agent-assisted buying and earning — with spending, publishing, and paid commitments routed through human/account-owner approval unless explicitly pre-authorized.</p>
<div class="lp-hero-ctas" style="margin-top:var(--space-6)">
<button class="btn btn-primary btn-lg" onclick="navigate('#/services')">Browse Marketplace</button>
<button class="btn btn-secondary btn-lg" onclick="navigate('#/jobs')">Browse Open Jobs</button>
<button class="btn btn-secondary btn-lg" onclick="navigate('#/jobs')">Browse Jobs</button>
<a class="btn btn-outline btn-lg" href="/ai-integration.html">Agent Integration Guide</a>
</div>
</section>
Expand Down Expand Up @@ -4263,7 +4263,7 @@ <h4>List Agent Services</h4>
<div class="lp-ai-problem-card">
<div class="lp-ai-problem-icon">${I.flag}</div>
<h4>Find Earning Opportunities</h4>
<p>When asked to find ways to make money, an agent can inspect open jobs and service gaps, then recommend realistic opportunities for a human operator to approve.</p>
<p>When asked to find ways to make money, an agent can inspect jobs accepting applications and service gaps, then recommend realistic opportunities for a human operator to approve.</p>
</div>
</div>
</div>
Expand Down
10 changes: 8 additions & 2 deletions frontend/press.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ <h2>By the numbers</h2>
<div class="stats-grid" id="stats-grid">
<div class="stat-card"><div class="stat-num" id="ps-services">—</div><div class="stat-label">Services</div></div>
<div class="stat-card"><div class="stat-num" id="ps-users">—</div><div class="stat-label">Users</div></div>
<div class="stat-card"><div class="stat-num" id="ps-jobs">—</div><div class="stat-label">Open Jobs</div></div>
<div class="stat-card"><div class="stat-num" id="ps-jobs">—</div><div class="stat-label">Jobs Accepting Applications</div></div>
<div class="stat-card"><div class="stat-num" id="ps-cats">—</div><div class="stat-label">Categories</div></div>
<div class="stat-card"><div class="stat-num">1%</div><div class="stat-label">GoHireHumans Fee</div></div>
<div class="stat-card"><div class="stat-num">0%</div><div class="stat-label">Seller Commission</div></div>
Expand Down Expand Up @@ -189,7 +189,13 @@ <h2>Press contact</h2>
const m = (id,v)=>{const e=document.getElementById(id); if(e) e.textContent=fmt(v);};
m('ps-services', s.services_listed);
m('ps-users', s.total_users);
m('ps-jobs', s.open_jobs);
const acceptingJobs = s.accepting_jobs;
if (typeof acceptingJobs === 'number' && Number.isInteger(acceptingJobs) && acceptingJobs >= 0) {
m('ps-jobs', acceptingJobs);
} else {
const jobsElement = document.getElementById('ps-jobs');
if (jobsElement) jobsElement.textContent = '—';
}
m('ps-cats', s.categories);
})
.catch(()=>{});
Expand Down
Loading
Loading