From 07b01c8260ff0f952a210bc0405bb560ca3237dc Mon Sep 17 00:00:00 2001 From: profilesearch <222277199+profilesearch@users.noreply.github.com> Date: Wed, 22 Jul 2026 02:32:17 -0700 Subject: [PATCH] fix: align marketplace inventory truth Distinguish literal open jobs from application-accepting jobs, classify admin applications by actionability, and remove stale public inventory claims. --- backend/api_core.py | 31 ++++++++-- backend/test_deep_audit_regressions.py | 74 ++++++++++++++++++++++- frontend/earn/open-paid-tasks.html | 13 +++- frontend/index.html | 10 +-- frontend/press.html | 10 ++- frontend/stats.html | 11 ++-- frontend/tests/browser-regression.spec.js | 58 ++++++++++++++++++ scripts/generate-marketplace-pulse.py | 12 ++-- 8 files changed, 193 insertions(+), 26 deletions(-) diff --git a/backend/api_core.py b/backend/api_core.py index 2c766fb..f515d27 100644 --- a/backend/api_core.py +++ b/backend/api_core.py @@ -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' @@ -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 @@ -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 @@ -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}) diff --git a/backend/test_deep_audit_regressions.py b/backend/test_deep_audit_regressions.py index c4ed745..9af1148 100644 --- a/backend/test_deep_audit_regressions.py +++ b/backend/test_deep_audit_regressions.py @@ -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() @@ -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" @@ -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") @@ -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", ], diff --git a/frontend/earn/open-paid-tasks.html b/frontend/earn/open-paid-tasks.html index 518cfa7..be6af9d 100644 --- a/frontend/earn/open-paid-tasks.html +++ b/frontend/earn/open-paid-tasks.html @@ -28,4 +28,15 @@ if (event.target.closest('.lp-mobile-link')) toggleMobileMenu(false); }); } -
Worker activation

Find open paid tasks you can apply to today.

Browse current jobs, apply with a short specific message, and keep work/payment on-platform.

Open paid task types

3

Open public jobs

Current starter jobs include website QA, lead research, and AI-output review.

Browse open jobs

What a strong application says

State the job you want, relevant experience, timing, and one example or link if available.

Stay on platform

Do not send private payment details or move work off-platform. Apply directly through GoHireHumans.

Good worker categories now

website testinglead researchAI fact-checkingdata cleanuplocal verification

List a matching service

\ No newline at end of file +
Worker activation

Find paid tasks you can apply to today.

Browse current jobs, apply with a short specific message, and keep work/payment on-platform.

Current paid task opportunities

Jobs accepting applications

Browse the live list for current QA, research, review, verification, and other scoped work.

Browse jobs

What a strong application says

State the job you want, relevant experience, timing, and one example or link if available.

Stay on platform

Do not send private payment details or move work off-platform. Apply directly through GoHireHumans.

Useful worker categories

website testinglead researchAI fact-checkingdata cleanuplocal verification

List a matching service

\ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 743a595..9f3ddfb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -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": [ @@ -1912,7 +1912,7 @@

Browse Jobs

: `

Post jobs and find great talent

`}
-
Loading open jobs…
+
Loading jobs…
${Array(5).fill(0).map(()=>`
`).join('')}
@@ -4211,10 +4211,10 @@

Audit Log

${I.bot} For AI Agents

A services marketplace
agents can actually read.

-

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.

+

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.

- + Agent Integration Guide
@@ -4263,7 +4263,7 @@

List Agent Services

${I.flag}

Find Earning Opportunities

-

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.

+

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.

diff --git a/frontend/press.html b/frontend/press.html index 600f1ed..037ae8a 100644 --- a/frontend/press.html +++ b/frontend/press.html @@ -129,7 +129,7 @@

By the numbers

Services
Users
-
Open Jobs
+
Jobs Accepting Applications
Categories
1%
GoHireHumans Fee
0%
Seller Commission
@@ -189,7 +189,13 @@

Press contact

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(()=>{}); diff --git a/frontend/stats.html b/frontend/stats.html index 93eb4be..5f5d2ad 100644 --- a/frontend/stats.html +++ b/frontend/stats.html @@ -6,7 +6,7 @@ Live Marketplace Stats — GoHireHumans - + @@ -123,7 +123,7 @@

Marketplace Stats

Services Listed
-
Open Jobs
+
Jobs Accepting Applications
Registered Users
Categories
Completed Orders
@@ -135,7 +135,7 @@

Services by category

-

Open jobs by category

+

Jobs accepting applications by category

@@ -175,7 +175,8 @@

Recently posted jobs

// Top numbers if (stats) { setText('s-services', fmt(stats.services_listed)); - setText('s-jobs', fmt(stats.open_jobs)); + const acceptingJobs = stats.accepting_jobs; + setText('s-jobs', typeof acceptingJobs === 'number' && Number.isInteger(acceptingJobs) && acceptingJobs >= 0 ? fmt(acceptingJobs) : '—'); setText('s-users', fmt(stats.total_users)); setText('s-cats', fmt(stats.categories)); setText('s-completed', fmt(stats.completed_orders)); @@ -191,7 +192,7 @@

Recently posted jobs

return; } const max = Math.max(...data.map(Number), 1); - holder.innerHTML = `