Skip to content

labor hub hot fixes for launch#4633

Merged
cemreinanc merged 2 commits into
mainfrom
fix/labor-hub-content-hotfix
Apr 20, 2026
Merged

labor hub hot fixes for launch#4633
cemreinanc merged 2 commits into
mainfrom
fix/labor-hub-content-hotfix

Conversation

@cemreinanc
Copy link
Copy Markdown
Contributor

@cemreinanc cemreinanc commented Apr 20, 2026

Summary by CodeRabbit

  • New Features

    • Added dynamic content displaying most and least vulnerable occupations for 2035 based on job forecast data.
  • Documentation

    • Added clarification that occupational mix charts display December values for each year.
  • Style

    • Updated wage growth descriptor from "notable" to "mild."

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@cemreinanc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a180043-3d5b-449f-9997-c1d20188ebe6

📥 Commits

Reviewing files that changed from the base of the PR and between 9667f1e and a5a3787.

📒 Files selected for processing (1)
  • front_end/src/app/(main)/labor-hub/page.tsx
📝 Walkthrough

Walkthrough

Added new helper functions to extract and compute job forecast data by year: getJobValueForYear retrieves forecast values, getTopExtremeJobsForYear returns top vulnerable jobs. Refactored job forecast logic from overview.tsx to shared helpers. Updated key_insights.tsx to dynamically display vulnerable occupations using new helpers. Added chart tooltip clarification in research.tsx.

Changes

Cohort / File(s) Summary
Helper Functions
front_end/src/app/(main)/labor-hub/helpers/fetch_jobs_data.ts
Added JobForecast type, VULNERABILITY_CHIP_JOB_LIMIT constant, and two new export functions: getJobValueForYear to extract per-year forecast values and getTopExtremeJobsForYear to compute and rank most/least vulnerable jobs, with de-duplication logic.
Component Refactoring
front_end/src/app/(main)/labor-hub/sections/overview.tsx
Removed local implementations of getJobValueForYear, getTopExtremeJobsForYear, and JobForecast type; updated to import and use these from the shared helpers module instead.
Dynamic Content Rendering
front_end/src/app/(main)/labor-hub/sections/key_insights.tsx
Integrated job forecast data fetching and per-2035 vulnerability computation. Added formatJobList helper to convert job arrays into readable text. Updated "Most and least vulnerable occupations" section to render dynamic content when available. Changed "Wages and hours worked" copy from "notable" to "mild" wage growth.
UI Documentation
front_end/src/app/(main)/labor-hub/sections/research.tsx
Added inline explanatory text to DefinitionTooltip for "Change in the occupational mix" chart, clarifying that data represents December values for each year.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hopping through code with organized cheer,
Job forecasts gathered, vulnerabilities clear,
From helpers they flow, both vulnerable and strong,
Dynamic insights render the year along!
With tooltips and wages, the labor hub shines bright!

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'labor hub hot fixes for launch' is vague and does not clearly describe the specific changes made, using generic language ('hot fixes') that obscures the actual implementation details of refactoring helper functions and updating UI text. Revise the title to be more specific about the main change, such as 'Refactor job forecast helpers and update vulnerable occupations UI' or 'Extract job forecast logic to shared helpers module'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/labor-hub-content-hotfix

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
front_end/src/app/(main)/labor-hub/sections/key_insights.tsx (1)

69-84: All fetches share failure: a single reject discards successful data too.

Promise.all rejects if any of the three fetches fails, so a transient failure in fetchJobsData will also drop overall2035 and insightsData, forcing the hard-coded fallback for unrelated copy (hours-worked, youth unemployment, etc.). Consider Promise.allSettled (or separate try/catch per fetch) so each insight degrades independently.

♻️ Proposed change
-  try {
-    const [overallData, fetchedInsights, { jobs }] = await Promise.all([
-      fetchOverallData(),
-      fetchKeyInsightsData(),
-      fetchJobsData(),
-    ]);
-    overall2035 = overallData.find((d) => d.year === 2035)?.value ?? null;
-    insightsData = fetchedInsights;
-    const extremes = getTopExtremeJobsForYear(jobs, "2035");
-    mostVulnerable2035 = extremes.mostVulnerable;
-    leastVulnerable2035 = extremes.leastVulnerable;
-  } catch (error) {
-    logError(error);
-  }
+  const [overallRes, insightsRes, jobsRes] = await Promise.allSettled([
+    fetchOverallData(),
+    fetchKeyInsightsData(),
+    fetchJobsData(),
+  ]);
+  if (overallRes.status === "fulfilled") {
+    overall2035 =
+      overallRes.value.find((d) => d.year === 2035)?.value ?? null;
+  } else logError(overallRes.reason);
+  if (insightsRes.status === "fulfilled") {
+    insightsData = insightsRes.value;
+  } else logError(insightsRes.reason);
+  if (jobsRes.status === "fulfilled") {
+    const extremes = getTopExtremeJobsForYear(jobsRes.value.jobs, "2035");
+    mostVulnerable2035 = extremes.mostVulnerable;
+    leastVulnerable2035 = extremes.leastVulnerable;
+  } else logError(jobsRes.reason);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front_end/src/app/`(main)/labor-hub/sections/key_insights.tsx around lines 69
- 84, The current Promise.all of the three fetches causes any single failure to
reject them all; update the code that calls Promise.all for fetchJobsData,
fetchOverall2035 (or fetchOverall2035Data), and fetchInsightsData to use
Promise.allSettled (or perform each fetch in its own try/catch) so each result
is handled independently: map settled results to success values or fallbacks and
only replace the specific insight with its fallback when that specific fetch
failed, leaving successful fetches intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front_end/src/app/`(main)/labor-hub/helpers/fetch_jobs_data.ts:
- Around line 53-81: The function getTopExtremeJobsForYear should normalize the
incoming limit before using it: validate that limit is finite, coerce to a
non-negative integer (e.g., Math.floor) and clamp it to at most the number of
available rows (rows.length) so slice(0, k) and the leastVulnerable loop can't
misbehave for negative, fractional, NaN or Infinity inputs; update the code that
computes k (and any uses of limit later) to use this sanitized value rather than
the raw limit parameter.

In `@front_end/src/app/`(main)/labor-hub/sections/key_insights.tsx:
- Around line 120-133: getTopExtremeJobsForYear currently returns the top N jobs
by value regardless of sign, so the UI sentence using leastVulnerable2035
assumes growth; update the rendering logic that builds the "... while
{leastVulnerableText} are projected to grow" phrase to first check whether all
entries in leastVulnerable2035 (from getTopExtremeJobsForYear) have positive
values (e.g., leastVulnerable2035.every(j => j.value > 0)) or use the same
sign/threshold logic used by the chart, and choose alternate phrasing (e.g.,
"are projected to decline the least" or neutral phrasing) when the condition is
false so the sentence remains factually correct.
- Around line 51-57: The plural wording branch is applied even when one of the
job lists has only one item; update the conditional that chooses the hard-coded
paragraph to require both job lists to have at least two items (e.g., check
topDeclineJobs.length >= 2 && topGrowJobs.length >= 2 or use the formatted
lists' lengths) before rendering the "are all expected..." sentence; if either
list has <2 items, fall back to the existing singular-aware branches (the same
change should be applied to the second block around lines 120-133). Ensure you
reference formatJobList and the topDeclineJobs/topGrowJobs (or their local
variable names) when making the check.

---

Nitpick comments:
In `@front_end/src/app/`(main)/labor-hub/sections/key_insights.tsx:
- Around line 69-84: The current Promise.all of the three fetches causes any
single failure to reject them all; update the code that calls Promise.all for
fetchJobsData, fetchOverall2035 (or fetchOverall2035Data), and fetchInsightsData
to use Promise.allSettled (or perform each fetch in its own try/catch) so each
result is handled independently: map settled results to success values or
fallbacks and only replace the specific insight with its fallback when that
specific fetch failed, leaving successful fetches intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d16b6e5-2f58-4e1d-891e-35d06d9ef1dc

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf7403 and 9667f1e.

📒 Files selected for processing (4)
  • front_end/src/app/(main)/labor-hub/helpers/fetch_jobs_data.ts
  • front_end/src/app/(main)/labor-hub/sections/key_insights.tsx
  • front_end/src/app/(main)/labor-hub/sections/overview.tsx
  • front_end/src/app/(main)/labor-hub/sections/research.tsx

Comment on lines +53 to +81
export function getTopExtremeJobsForYear(
jobs: JobWithPost[],
yearLabel: string,
limit: number = VULNERABILITY_CHIP_JOB_LIMIT
): { mostVulnerable: JobForecast[]; leastVulnerable: JobForecast[] } {
const rows: JobForecast[] = [];
for (const job of jobs) {
const v = getJobValueForYear(job, yearLabel);
if (v == null) continue;
rows.push({ name: job.name, value: v });
}

if (!rows.length) {
return { mostVulnerable: [], leastVulnerable: [] };
}

const sorted = [...rows].sort((a, b) => a.value - b.value);
const n = sorted.length;
const k = Math.min(limit, n);
const mostVulnerable = sorted.slice(0, k);
const mostNames = new Set(mostVulnerable.map((j) => j.name));
const leastVulnerable: JobForecast[] = [];
for (let i = n - 1; i >= 0 && leastVulnerable.length < limit; i--) {
const j = sorted[i];
if (j == null) continue;
if (!mostNames.has(j.name)) leastVulnerable.push(j);
}

return { mostVulnerable, leastVulnerable };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize limit before slicing.

limit is typed as number, so callers can still pass negative, fractional, or non-finite values at runtime. For example, limit = -1 makes slice(0, -1) return nearly every row as “most vulnerable.” Clamp it to a non-negative integer before using it.

Proposed fix
 export function getTopExtremeJobsForYear(
   jobs: JobWithPost[],
   yearLabel: string,
   limit: number = VULNERABILITY_CHIP_JOB_LIMIT
 ): { mostVulnerable: JobForecast[]; leastVulnerable: JobForecast[] } {
+  const safeLimit = Number.isFinite(limit)
+    ? Math.max(0, Math.floor(limit))
+    : VULNERABILITY_CHIP_JOB_LIMIT;
   const rows: JobForecast[] = [];
   for (const job of jobs) {
     const v = getJobValueForYear(job, yearLabel);
     if (v == null) continue;
     rows.push({ name: job.name, value: v });
@@
 
   const sorted = [...rows].sort((a, b) => a.value - b.value);
   const n = sorted.length;
-  const k = Math.min(limit, n);
+  const k = Math.min(safeLimit, n);
   const mostVulnerable = sorted.slice(0, k);
   const mostNames = new Set(mostVulnerable.map((j) => j.name));
   const leastVulnerable: JobForecast[] = [];
-  for (let i = n - 1; i >= 0 && leastVulnerable.length < limit; i--) {
+  for (let i = n - 1; i >= 0 && leastVulnerable.length < safeLimit; i--) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front_end/src/app/`(main)/labor-hub/helpers/fetch_jobs_data.ts around lines
53 - 81, The function getTopExtremeJobsForYear should normalize the incoming
limit before using it: validate that limit is finite, coerce to a non-negative
integer (e.g., Math.floor) and clamp it to at most the number of available rows
(rows.length) so slice(0, k) and the leastVulnerable loop can't misbehave for
negative, fractional, NaN or Infinity inputs; update the code that computes k
(and any uses of limit later) to use this sanitized value rather than the raw
limit parameter.

Comment on lines +51 to +57
function formatJobList(jobs: JobForecast[]): string | null {
const names = jobs.map((j) => j.name);
if (names.length === 0) return null;
if (names.length === 1) return names[0] ?? null;
if (names.length === 2) return `${names[0]} and ${names[1]}`;
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Grammar breaks for single-item lists.

formatJobList can return a single name (length === 1), but the surrounding copy reads "{X} are all expected to see the largest decreases ... while {Y} are projected to grow." With one job, this becomes "Software developers are all expected..." — plural/"all" doesn't agree with a singular subject. Given VULNERABILITY_CHIP_JOB_LIMIT almost always yields ≥3 items this is largely defensive, but since you wrote explicit 0/1/2 branches it's worth making the fallback condition stricter (e.g., require both lists to have ≥2 items) so the hard-coded paragraph is used in degenerate cases.

-  const mostVulnerableText = formatJobList(mostVulnerable2035);
-  const leastVulnerableText = formatJobList(leastVulnerable2035);
+  const mostVulnerableText =
+    mostVulnerable2035.length >= 2 ? formatJobList(mostVulnerable2035) : null;
+  const leastVulnerableText =
+    leastVulnerable2035.length >= 2 ? formatJobList(leastVulnerable2035) : null;

Also applies to: 120-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front_end/src/app/`(main)/labor-hub/sections/key_insights.tsx around lines 51
- 57, The plural wording branch is applied even when one of the job lists has
only one item; update the conditional that chooses the hard-coded paragraph to
require both job lists to have at least two items (e.g., check
topDeclineJobs.length >= 2 && topGrowJobs.length >= 2 or use the formatted
lists' lengths) before rendering the "are all expected..." sentence; if either
list has <2 items, fall back to the existing singular-aware branches (the same
change should be applied to the second block around lines 120-133). Ensure you
reference formatJobList and the topDeclineJobs/topGrowJobs (or their local
variable names) when making the check.

Comment on lines +120 to +133
{mostVulnerableText && leastVulnerableText ? (
<>
{mostVulnerableText} are all expected to see the largest decreases
in employment rates, while {leastVulnerableText} are projected to
grow.
</>
) : (
<>
Software developers, lawyers and law clerks, and laborers and
material movers are all expected to see the largest decreases in
employment rates, while registered nurses, K-12 teachers, and
restaurant servers are projected to grow.
</>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dynamic "least vulnerable" copy asserts growth without checking sign.

getTopExtremeJobsForYear returns the limit highest-valued jobs regardless of whether their forecast values are positive. If the overall outlook worsens and even the top-ranked jobs have values ≤ 0, the rendered sentence "... while {leastVulnerableText} are projected to grow" will be factually wrong. Consider guarding the phrasing on leastVulnerable2035.every(j => j.value > 0) (or pulling the threshold from the same logic that powers the chart) before using the "projected to grow" wording.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front_end/src/app/`(main)/labor-hub/sections/key_insights.tsx around lines
120 - 133, getTopExtremeJobsForYear currently returns the top N jobs by value
regardless of sign, so the UI sentence using leastVulnerable2035 assumes growth;
update the rendering logic that builds the "... while {leastVulnerableText} are
projected to grow" phrase to first check whether all entries in
leastVulnerable2035 (from getTopExtremeJobsForYear) have positive values (e.g.,
leastVulnerable2035.every(j => j.value > 0)) or use the same sign/threshold
logic used by the chart, and choose alternate phrasing (e.g., "are projected to
decline the least" or neutral phrasing) when the condition is false so the
sentence remains factually correct.

@cemreinanc cemreinanc merged commit 81b9007 into main Apr 20, 2026
11 of 12 checks passed
@cemreinanc cemreinanc deleted the fix/labor-hub-content-hotfix branch April 20, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant