# Building a Job Search

This guide walks through implementing job search functionality with Cubit, from basic keyword matching to advanced semantic discovery.

## Basic Keyword Search

The simplest approach-search jobs by title:

```python
from cubit import CubitClient

client = CubitClient("cubit_your_key")

# Simple keyword search
results = client.search_jobs("nurse", limit=10)

print(f"Found {results['count']} jobs matching 'nurse':\n")
for job in results["jobs"]:
    print(f"{job['title']}")
    print(f"  SOC: {job['soc_code']}")
    print(f"  Automation Risk: {job['automation_susceptibility_score']:.0f}/100")
    print(f"  Balanced Impact: {job['balanced_impact_score']:+.0f}")
    print()
```

**Output:**

```
Found 10 jobs matching 'nurse':

Registered Nurses
  SOC: 29-1141.00
  Automation Risk: 47/100
  Balanced Impact: +23

Licensed Practical and Licensed Vocational Nurses
  SOC: 29-2061.00
  Automation Risk: 45/100
  Balanced Impact: +17

Nurse Practitioners
  SOC: 29-1171.00
  Automation Risk: 44/100
  Balanced Impact: +27
...
```

## Semantic Search (Enterprise)

Natural language queries that understand meaning, not just keywords. Requires Enterprise tier.

```python
# Users can search however they think about careers
results = client.search_jobs_semantic(
    "careers for people who love animals and working outdoors",
    limit=10
)

print("Jobs matching your description:\n")
for job in results["jobs"]:
    print(f"{job['title']} - {job['relevance_score']:.0%} match")
    print(f"  Balanced Impact: {job['balanced_impact_score']:+.0f}")
```

**Output:**

```
Jobs matching your description:

Animal Caretakers - 45% match
  Balanced Impact: +10

Animal Control Workers - 44% match
  Balanced Impact: +12

Animal Trainers - 43% match
  Balanced Impact: +7

Zoologists and Wildlife Biologists - 43% match
  Balanced Impact: +15
```

### Search Scope Options

```python
# Search job titles/descriptions (default)
results = client.search_jobs_semantic(query, search_scope="jobs")

# Search at task level, aggregate to jobs
results = client.search_jobs_semantic(query, search_scope="tasks")

# Search both
results = client.search_jobs_semantic(query, search_scope="both")
```

Task-level search finds jobs where specific activities match your query, even if the job title doesn't.

## Building a Search UI

### React Component Example

```typescript
import { useState } from 'react';

interface Job {
  soc_code: string;
  title: string;
  automation_susceptibility_score: number;
  human_centric_resilience_score: number;
  balanced_impact_score: number;
  relevance_score?: number;
}

function JobSearch() {
  const [query, setQuery] = useState('');
  const [jobs, setJobs] = useState<Job[]>([]);
  const [loading, setLoading] = useState(false);
  const [useSemanticSearch, setUseSemanticSearch] = useState(true);

  const search = async () => {
    setLoading(true);
    
    const endpoint = useSemanticSearch 
      ? `/api/jobs/search/semantic?q=${encodeURIComponent(query)}`
      : `/api/jobs/search?q=${encodeURIComponent(query)}`;
    
    const res = await fetch(endpoint);
    const data = await res.json();
    setJobs(data.jobs);
    setLoading(false);
  };

  return (
    <div className="job-search">
      <div className="search-bar">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder={useSemanticSearch 
            ? "Describe your ideal career..." 
            : "Search by job title..."}
          onKeyPress={(e) => e.key === 'Enter' && search()}
        />
        <button onClick={search} disabled={loading}>
          {loading ? 'Searching...' : 'Search'}
        </button>
      </div>
      
      <label>
        <input
          type="checkbox"
          checked={useSemanticSearch}
          onChange={(e) => setUseSemanticSearch(e.target.checked)}
        />
        Use AI-powered semantic search
      </label>

      <div className="results">
        {jobs.map(job => (
          <JobCard key={job.soc_code} job={job} />
        ))}
      </div>
    </div>
  );
}

function JobCard({ job }: { job: Job }) {
  const impactColor = job.balanced_impact_score > 0 ? 'green' : 
                      job.balanced_impact_score < 0 ? 'red' : 'gray';
  
  return (
    <div className="job-card">
      <h3>{job.title}</h3>
      {job.relevance_score && (
        <span className="match">{(job.relevance_score * 100).toFixed(0)}% match</span>
      )}
      <div className="scores">
        <div>
          <label>Automation Risk</label>
          <meter value={job.automation_susceptibility_score} max={100} />
          <span>{job.automation_susceptibility_score.toFixed(0)}/100</span>
        </div>
        <div>
          <label>Human Resilience</label>
          <meter value={job.human_centric_resilience_score} max={100} />
          <span>{job.human_centric_resilience_score.toFixed(0)}/100</span>
        </div>
        <div style={{ color: impactColor }}>
          <label>Net Impact</label>
          <span>{job.balanced_impact_score > 0 ? '+' : ''}{job.balanced_impact_score.toFixed(0)}</span>
        </div>
      </div>
      <a href={`/jobs/${job.soc_code}`}>View Details -></a>
    </div>
  );
}
```

### Backend API Route (Next.js)

```typescript
// app/api/jobs/search/semantic/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const query = request.nextUrl.searchParams.get('q');
  const limit = request.nextUrl.searchParams.get('limit') || '10';
  
  if (!query) {
    return NextResponse.json({ error: 'Query required' }, { status: 400 });
  }

  const response = await fetch(
    `https://api.maidenlabs.tools/jobs/search/semantic?q=${encodeURIComponent(query)}&limit=${limit}`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.CUBIT_API_KEY}`,
      },
    }
  );

  const data = await response.json();
  return NextResponse.json(data);
}
```

## Filtering and Sorting

### By Risk Level

```python
# Get all search results, then filter client-side
results = client.search_jobs_semantic("technology careers", limit=50)

# Filter to low-risk jobs
low_risk_jobs = [
    job for job in results["jobs"]
    if job["balanced_impact_score"] > 10
]

print("Low-risk technology careers:")
for job in low_risk_jobs:
    print(f"  {job['title']}: {job['balanced_impact_score']:+.0f}")
```

### By Quadrant

```python
# Find augmentation-zone jobs
results = client.search_jobs_semantic("data and analytics", limit=30)

for job in results["jobs"]:
    # Get full profile to access quadrant
    profile = client.get_job(job["soc_code"])
    if profile["classification"]["quadrant"] == "augmentation":
        print(f"Augmentation: {profile['title']}")
```

## Comparing Search Results

```python
# Compare jobs side by side (Professional+)
results = client.search_jobs("analyst", limit=5)
soc_codes = [job["soc_code"] for job in results["jobs"]]

comparison = client.compare_jobs(soc_codes)

print("Analyst Roles Compared:\n")
print(f"{'Title':<40} {'Auto Risk':>10} {'Resilience':>10} {'Impact':>10}")
print("-" * 72)

for job in comparison["comparison"]:
    print(f"{job['title']:<40} {job['automation_susceptibility_score']:>10.0f} "
          f"{job['human_centric_resilience_score']:>10.0f} "
          f"{job['balanced_impact_score']:>+10.0f}")
```

## Caching Strategies

For production applications:

```python
import redis
import json
from cubit import CubitClient

redis_client = redis.Redis()
cubit = CubitClient("cubit_your_key")

def search_jobs_cached(query: str, ttl: int = 3600) -> dict:
    """Search with Redis caching."""
    cache_key = f"cubit:search:{query.lower()}"
    
    # Check cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # Query API
    results = cubit.search_jobs_semantic(query, limit=20)
    
    # Cache results
    redis_client.setex(cache_key, ttl, json.dumps(results))
    
    return results
```

## Error Handling

```python
from cubit import (
    CubitClient, 
    AuthenticationError, 
    AuthorizationError,
    RateLimitError,
    NotFoundError
)

client = CubitClient("cubit_your_key")

try:
    results = client.search_jobs_semantic("my query")
    
except AuthorizationError:
    # User is on sandbox tier, fall back to keyword search
    results = client.search_jobs("my query")
    
except RateLimitError as e:
    # Handle rate limiting
    print(f"Rate limited. Retry after {e.retry_after} seconds")
    
except AuthenticationError:
    # API key is invalid
    raise
```

## Next Steps

* [Displaying Risk Scores](/guides/risk-scores.md) - Visualizing job vulnerability
* [Task-Level Analysis](/guides/task-analysis.md) - Drill into specific work activities
* [Career Transitions](/guides/career-transitions.md) - Finding resilient alternatives


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.maidenlabs.tools/guides/job-search.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
