← Back to Modules

dbbasic-jobs Specification

Version: 1.0 Status: Specification Author: DBBasic Project Date: November 2025

Links: - PyPI: https://pypi.org/project/dbbasic-jobs/ (not yet published) - GitHub: https://github.com/askrobots/dbbasic-jobs (not yet published) - Specification: http://dbbasic.com/jobs-spec


Philosophy

"A job board is three states: open, assigned, done. Everything else is noise."

The critical insight: Most job boards over-complicate simple workflows. Freelancer marketplaces need: post job → receive applications → pick winner → mark complete. That's it.

Design Principles

  1. Status-Driven: Jobs have clear states (open, in_progress, completed, cancelled)
  2. Application-Based: Freelancers apply, clients choose
  3. Proposal System: Applications include bid amount and proposal
  4. Integration-Ready: Works with dbbasic-ratings, dbbasic-accounts
  5. grep-able: All data in TSV files

The Problem with Existing Job Boards

Too Complex: Upwork

- Connects, invites, proposals, interviews, contracts, milestones
- Escrow, time tracking, screenshots, weekly limits
- Agency vs individual, fixed vs hourly
- 47 different status states

Result: Takes 30 minutes to post a simple job.

Too Simple: Craigslist

- Post job
- Get email replies
- No tracking, no escrow, no reputation

Result: Spam, scams, no quality control.

The Sweet Spot: Status + Applications + Ratings

1. Client posts job (title, description, budget)
2. Freelancers apply (bid, proposal)
3. Client picks winner
4. Work happens
5. Both parties rate each other

Why this works: - Clear workflow - No artificial complexity - Integrates with reputation system - Simple enough to understand in 2 minutes


TSV Storage Schema

jobs.tsv

id  client_id   title   description budget_min  budget_max  status  category    skills  created_at  updated_at  awarded_to  completed_at

Fields: - id: Unique job ID (e.g., "j001", "j002") - client_id: User who posted the job - title: Job title (e.g., "Build a Django REST API") - description: Full job description (markdown supported) - budget_min: Minimum budget ($) or null for open - budget_max: Maximum budget ($) or null for open - status: Job status (open, in_progress, completed, cancelled) - category: Job category (web_dev, design, writing, etc.) - skills: Comma-separated required skills - created_at: When job was posted - updated_at: When job was last modified - awarded_to: User ID of selected freelancer (null if not awarded) - completed_at: When job was marked complete

Example Data

id  client_id   title   description budget_min  budget_max  status  category    skills  created_at  updated_at  awarded_to  completed_at
j001    u042    Build Django REST API   Need REST API for mobile app... 2000    5000    completed   web_dev python,django,rest  2025-10-15T10:00:00 2025-11-01T15:00:00 u055    2025-11-01T15:00:00
j002    u042    Logo design Modern logo for SaaS startup    500 1000    in_progress design  illustrator,photoshop   2025-10-20T14:00:00 2025-10-25T09:00:00 u088    null
j003    u099    Write blog posts    10 SEO-optimized blog posts null    null    open    writing seo,content 2025-11-01T08:00:00 2025-11-01T08:00:00 null    null

applications.tsv

id  job_id  applicant_id    bid_amount  proposal    status  created_at  updated_at

Fields: - id: Unique application ID (e.g., "app001", "app002") - job_id: Job being applied to - applicant_id: User applying - bid_amount: Proposed price ($) - proposal: Application cover letter/proposal - status: Application status (pending, accepted, rejected, withdrawn) - created_at: When application was submitted - updated_at: When application was last modified

Example Data

id  job_id  applicant_id    bid_amount  proposal    status  created_at  updated_at
app001  j001    u055    3500    I have 5 years Django experience... accepted    2025-10-15T11:00:00 2025-10-16T10:00:00
app002  j001    u077    4200    Senior dev, can start immediately...    rejected    2025-10-15T12:00:00 2025-10-16T10:00:00
app003  j002    u088    750 Award-winning designer...   accepted    2025-10-20T15:00:00 2025-10-25T09:00:00
app004  j003    u091    800 SEO specialist, 500+ articles...    pending 2025-11-01T09:00:00 2025-11-01T09:00:00
app005  j003    u092    1200    Content strategist...   pending 2025-11-01T10:00:00 2025-11-01T10:00:00

Core API

Python API

from dbbasic_jobs import Jobs

# Initialize
jobs = Jobs('/path/to/data')

# Create job
job_id = jobs.create(
    client_id='u042',
    title='Build Django REST API',
    description='Need REST API for mobile app with auth, user profiles, etc.',
    budget_min=2000,
    budget_max=5000,
    category='web_dev',
    skills=['python', 'django', 'rest']
)

# Get job
job = jobs.get('j001')

# List jobs
all_jobs = jobs.list(status='open')
my_jobs = jobs.list(client_id='u042')
category_jobs = jobs.list(category='web_dev')

# Update job
jobs.update('j001', status='in_progress', awarded_to='u055')

# Delete job
jobs.delete('j001')

# Apply to job
app_id = jobs.apply(
    job_id='j001',
    applicant_id='u055',
    bid_amount=3500,
    proposal='I have 5 years Django experience...'
)

# Get applications for job
apps = jobs.get_applications('j001')

# Get applications by user
my_apps = jobs.get_applications_by_user('u055')

# Award job to applicant
jobs.award(job_id='j001', applicant_id='u055')

# Mark job complete
jobs.complete('j001')

# Cancel job
jobs.cancel('j001')

Job Status Flow

     ┌──────────┐
     │   OPEN   │
     └─────┬────┘
           │
           │ Client awards job
           ▼
   ┌──────────────┐
   │ IN_PROGRESS  │
   └──────┬───────┘
          │
          │ Client marks complete
          ▼
    ┌──────────┐
    │COMPLETED │
    └──────────┘

    (From any state)
          │
          │ Client cancels
          ▼
    ┌──────────┐
    │CANCELLED │
    └──────────┘

State Transitions

VALID_TRANSITIONS = {
    'open': ['in_progress', 'cancelled'],
    'in_progress': ['completed', 'cancelled'],
    'completed': [],  # Terminal state
    'cancelled': []   # Terminal state
}

Application Status Flow

     ┌──────────┐
     │ PENDING  │
     └────┬─────┘
          │
          ├─────→ ACCEPTED (job awarded)
          │
          ├─────→ REJECTED (client declined)
          │
          └─────→ WITHDRAWN (applicant withdrew)

Standard Job Categories

CATEGORIES = {
    'web_dev': 'Web Development',
    'mobile_dev': 'Mobile Development',
    'design': 'Design & Creative',
    'writing': 'Writing & Content',
    'marketing': 'Marketing & SEO',
    'data_science': 'Data Science & Analytics',
    'video': 'Video & Animation',
    'audio': 'Audio & Music',
    'admin': 'Admin & Support',
    'consulting': 'Consulting',
    'other': 'Other'
}

Budget Handling

Fixed Budget

jobs.create(
    budget_min=1000,
    budget_max=1000,
    ...
)
# Display: "$1,000 fixed price"

Budget Range

jobs.create(
    budget_min=1000,
    budget_max=5000,
    ...
)
# Display: "$1,000 - $5,000"

Open Budget

jobs.create(
    budget_min=None,
    budget_max=None,
    ...
)
# Display: "Budget: Open to proposals"

Skills Handling

Skills stored as comma-separated string:

# Create with skills
jobs.create(
    skills=['python', 'django', 'postgresql', 'rest']
)

# Stored as: "python,django,postgresql,rest"

# Search by skill
python_jobs = jobs.search_by_skill('python')

# Search by multiple skills (AND)
jobs_with_skills = jobs.search_by_skills(['python', 'django'])

Web Integration

With dbbasic-web

# api/jobs/create.py
from dbbasic_jobs import Jobs
from dbbasic_web.responses import json_response

def handle(request):
    jobs = Jobs('/data')

    job_id = jobs.create(
        client_id=request.user['id'],
        title=request.form['title'],
        description=request.form['description'],
        budget_min=int(request.form.get('budget_min', 0)) or None,
        budget_max=int(request.form.get('budget_max', 0)) or None,
        category=request.form['category'],
        skills=request.form['skills'].split(',')
    )

    return json_response({'job_id': job_id})

# api/jobs/apply.py
def handle(request):
    jobs = Jobs('/data')

    app_id = jobs.apply(
        job_id=request.form['job_id'],
        applicant_id=request.user['id'],
        bid_amount=int(request.form['bid_amount']),
        proposal=request.form['proposal']
    )

    return json_response({'application_id': app_id})

# api/jobs/award.py
def handle(request):
    jobs = Jobs('/data')

    # Only job owner can award
    job = jobs.get(request.form['job_id'])
    if job['client_id'] != request.user['id']:
        return json_response({'error': 'Unauthorized'}, status=403)

    jobs.award(
        job_id=request.form['job_id'],
        applicant_id=request.form['applicant_id']
    )

    return json_response({'success': True})

Display Components

<!-- Job listing card -->
<div class="job-card">
    <h3>{{ job.title }}</h3>
    <div class="job-meta">
        <span class="category">{{ job.category }}</span>
        <span class="budget">${{ job.budget_min }} - ${{ job.budget_max }}</span>
        <span class="posted">{{ job.created_at | timeago }}</span>
    </div>
    <p>{{ job.description | truncate(200) }}</p>
    <div class="skills">
        {% for skill in job.skills %}
        <span class="skill-tag">{{ skill }}</span>
        {% endfor %}
    </div>
    <a href="/jobs/{{ job.id }}" class="btn">View Details</a>
</div>

<!-- Application form -->
<form method="POST" action="/api/jobs/apply">
    <input type="hidden" name="job_id" value="{{ job.id }}">

    <label>Your Bid ($)</label>
    <input type="number" name="bid_amount" required>

    <label>Proposal</label>
    <textarea name="proposal" rows="10" required></textarea>

    <button type="submit">Submit Application</button>
</form>

CLI Commands

List jobs

# All jobs
dbbasic jobs list

# By status
dbbasic jobs list --status open

# By category
dbbasic jobs list --category web_dev

# By client
dbbasic jobs list --client u042

Create job

dbbasic jobs create \
    --client u042 \
    --title "Build Django REST API" \
    --description "Need REST API for mobile app" \
    --budget-min 2000 \
    --budget-max 5000 \
    --category web_dev \
    --skills "python,django,rest"

View applications

# Applications for job
dbbasic jobs applications j001

# Applications by user
dbbasic jobs applications --user u055

Award job

dbbasic jobs award j001 --to u055

Mark complete

dbbasic jobs complete j001

Admin Interface

Auto-discovered by dbbasic-admin:

# Automatic CRUD interface at /admin/jobs/
# Shows:
# - Recent jobs
# - Filter by status, category, budget range
# - Applications per job
# - Award/complete/cancel actions
# - Job analytics (avg budget, time to award, etc.)

Integration with Other Modules

With dbbasic-ratings

After job completion, enable ratings:

from dbbasic_jobs import Jobs
from dbbasic_ratings import Ratings

jobs = Jobs('/data')
ratings = Ratings('/data')

# Mark job complete
jobs.complete('j001')

# Get job details
job = jobs.get('j001')

# Enable rating period
ratings.enable_rating_period(
    project_id=job['id'],
    user_a=job['client_id'],
    user_b=job['awarded_to'],
    duration_days=30
)

With dbbasic-accounts

Show user reputation on job listings:

from dbbasic_jobs import Jobs
from dbbasic_ratings import Ratings

jobs = Jobs('/data')
ratings = Ratings('/data')

# Get job applications with reputation
apps = jobs.get_applications('j001')
for app in apps:
    user_rep = ratings.get_reputation(app['applicant_id'])
    app['reputation'] = user_rep['overall_score']

With dbbasic-follows

Notify followers when new job posted:

from dbbasic_jobs import Jobs
from dbbasic_follows import Follows
from dbbasic_email import Email

jobs = Jobs('/data')
follows = Follows('/data')
email = Email('/data')

# Create job
job_id = jobs.create(...)

# Notify followers
followers = follows.get_followers(client_id)
for follower_id in followers:
    email.send(
        to=follower_id,
        subject=f"New job posted by {client_id}",
        body=f"Check out this new job: {job_url}"
    )

Search and Filtering

By Skills

# Jobs requiring Python
python_jobs = jobs.search_by_skill('python')

# Jobs requiring Python AND Django
python_django_jobs = jobs.search_by_skills(['python', 'django'])

By Budget Range

# Jobs with budget $1000-$5000
jobs_in_range = jobs.search_by_budget(min_budget=1000, max_budget=5000)

By Category

# All web dev jobs
web_jobs = jobs.list(category='web_dev')

Full Text Search

# Search in title and description
results = jobs.search('REST API mobile app')

Advanced Features

1. Featured Jobs

Add a featured field for promoted listings:

jobs.create(..., featured=True)

# Featured jobs appear first
featured = jobs.list(featured=True)

2. Job Templates

Save common job templates:

# Save template
jobs.save_template(
    name='Django API Project',
    template={
        'category': 'web_dev',
        'skills': ['python', 'django', 'rest'],
        'description_template': '...'
    }
)

# Use template
jobs.create_from_template('Django API Project', budget_min=2000, ...)

3. Milestone-Based Jobs

Add milestone support for large projects:

# Create job with milestones
jobs.create(
    ...,
    milestones=[
        {'name': 'Database design', 'amount': 1000},
        {'name': 'API implementation', 'amount': 2000},
        {'name': 'Testing & deployment', 'amount': 500}
    ]
)

4. Application Deadlines

jobs.create(
    ...,
    application_deadline='2025-12-01T23:59:59'
)

# Auto-close applications after deadline
jobs.close_expired_applications()

Implementation Size

Target: ~200 lines of Python

dbbasic_jobs/
├── __init__.py         # 10 lines
├── jobs.py             # 100 lines (CRUD, status management)
├── applications.py     # 60 lines (application handling)
├── search.py           # 20 lines (filtering, search)
└── cli.py             # 10 lines (command-line interface)

Total: ~200 lines

Security Considerations

1. Authorization

Only job owner can: - Update job - Award job - Mark complete - Cancel job

def award(self, job_id, applicant_id, requesting_user_id):
    job = self.get(job_id)
    if job['client_id'] != requesting_user_id:
        raise PermissionError('Only job owner can award')
    # ...

2. Application Limits

Prevent spam applications:

# Max 10 pending applications per user
pending_apps = self.get_applications_by_user(user_id, status='pending')
if len(pending_apps) >= 10:
    raise ValueError('Too many pending applications')

3. Status Validation

Ensure valid state transitions:

if current_status not in VALID_TRANSITIONS:
    raise ValueError(f'Cannot transition from {current_status}')
if new_status not in VALID_TRANSITIONS[current_status]:
    raise ValueError(f'Cannot transition from {current_status} to {new_status}')

grep-able Data

# Find all open jobs
grep "open" data/jobs.tsv

# Jobs in web_dev category
grep "web_dev" data/jobs.tsv

# Jobs posted by user u042
grep "u042" data/jobs.tsv

# Applications by user u055
grep "u055" data/applications.tsv

# Accepted applications
grep "accepted" data/applications.tsv

Why This Design?

1. Simple Status Model

Only 4 states: open, in_progress, completed, cancelled

Most job boards have 15+ states that confuse users: - "Under review" - "Interviewing" - "Contract pending" - "On hold" - etc.

We skip all that. You're either working on it or you're not.

2. Application-Based (Not Invites)

Freelancers apply to jobs (pull model), rather than clients inviting freelancers (push model).

Why? Simpler workflow, less spam, better match quality.

3. Bidding System

Applications include bid amount, allowing: - Price discovery - Competition - Client choice

4. Integration-First

Works seamlessly with: - dbbasic-ratings (reputation) - dbbasic-accounts (user management) - dbbasic-follows (notifications) - dbbasic-email (updates)


Comparison to Existing Systems

Feature Upwork Fiverr Craigslist dbbasic-jobs
Lines of Code ~500K ~500K ~50K ~200
Job Status States 15+ 10+ 2 4
Application System Yes No (gigs) No Yes
Reputation Yes Yes No Yes (via dbbasic-ratings)
Escrow Yes Yes No Optional (integration)
TSV Storage No No No Yes
grep-able No No No Yes

Status

Version: 1.0 Specification Implementation: Not yet started Expected Size: ~200 lines Dependencies: dbbasic-tsv only

Optional Integrations: - dbbasic-ratings (reputation) - dbbasic-accounts (user management) - dbbasic-email (notifications) - dbbasic-follows (social features)


Last Updated: November 2025