Artificial Intelligence · HR Tech · Argentina

AI that transforms recruitment in the energy sector

Automatic CV analysis pipeline for the Chamber of Gas Stations and Garages in Rosario, Argentina. From 15 minutes of manual reading to 45 seconds of intelligent analysis per applicant.

-94%
screening time per CV
<45s
full analysis per application
×8.8
HR capacity per analyst
8:1
estimated year 1 ROI

A sector chamber that adopted AI to scale its operations

CESGAR connects job seekers with energy-automotive sector companies in Rosario. With dozens of CVs per week and complex selection criteria, automation was imperative.

The system centralizes all CESGAR operations: member management, news, procedures, benefits, and — the most innovative component — a job board with AI-powered automatic CV screening.

When an applicant uploads their CV through the web form, a Django signal fires an asynchronous Celery task that extracts text from the PDF, analyzes it with an LLM, and applies deterministic Python business rules to generate an objective score from 1 to 5, with a textual justification ready for the HR team.

The WhatsApp mass messaging component complements the system, enabling institutional communications with a high open rate (87%) replacing traditional email.

Gas stations Garages HR Tech WhatsApp Business
# AI pipeline architecture

Web form
   │ applicant uploads CV
   ▼
post_save signal (Django)
   │ fires async task
   ▼
Celery Worker
   │
   ├─ PDFProcessor
   │    └─ OCR fallback if scanned
   │
   ├─ DeepSeek LLM
   │    └─ structured JSON extraction
   │    └─ holistic 1–5 assessment
   │
   └─ cv_scoring.py (pure Python)
        └─ deterministic rules
        └─ auto discard
        └─ sector bonuses

HR Admin Panel
   └─ score + report + ranking

Three applied intelligence modules

Each component solves a concrete problem in the chamber's operational flow.

Structured CV extraction

DeepSeek LLM analyzes the applicant's PDF and extracts key fields: date of birth, address, education level, work experience with duration, schedule availability, and relevant training. Includes OCR fallback for scanned documents.

DeepSeek · OCR · JSON

Hybrid LLM + Python scoring

The LLM assigns a holistic qualitative assessment. Python applies deterministic rules: automatic discard, 5 weighted modules, sector bonuses, and company-configurable parameters. Result: an auditable 1–5 score.

Deterministic · Auditable · Configurable

WhatsApp mass messaging

Meta-approved templates with visual editor, customizable variables, and real-time preview. Async sending to tagged contacts with per-message tracking. Types: text, call-to-action, and quick reply.

Twilio · Celery · Real-time tracking

Per-company parameters

Each member station can specify its own criteria: geographic area, age range, required experience (must have) or prohibited experience (must not have). The same scoring engine produces results adapted to each context.

Multi-tenant · No-code

HR panel with ranking

The team sees candidates already scored and ranked, with an LLM-generated natural language summary. Access to per-module detail to audit any score. One-click reprocessing.

Django Unfold · Customized admin

Guaranteed discard rules

Automatic discard rules run in Python, not in the LLM. 100% consistency guaranteed: model hallucinations cannot affect a discard decision.

Zero hallucinations on discards

Stack, design decisions, and code

Architecture oriented toward cost-efficiency, reliability, and maintainability for a small team.

Django 5.1 + Python 3.13Built-in admin eliminates custom backoffice. ORM + signals for decoupling.
Celery + RedisAsync CV processing without blocking the HTTP cycle. Real-time cancellation.
DeepSeek APILLM 10–15× cheaper than GPT-4 with comparable quality for extraction in Spanish.
Cloudflare R2CV storage with no egress fees. Critical at scale vs. AWS S3.
Twilio Content APIOfficial abstraction for Meta-approved templates. Prevents account bans.
PostgreSQL + JSONBPrimary persistence. JSONB for reports and template variables.
# Signal that decouples saving from AI analysis
# The web form responds immediately; analysis happens in background

@receiver(post_save, sender=JobApplication)
def on_application_saved(sender, instance, created, **kwargs):
    if created and instance.resume_file:
        analyze_cv.delay(instance.id)  # does not block the HTTP request

@shared_task
def analyze_cv(application_id: int, **params):
    application = JobApplication.objects.get(id=application_id)
    # Read CV from R2 using BytesIO (avoids failures in Celery context)
    buffer = BytesIO()
    with application.resume_file.open("rb") as file:
        buffer.write(file.read())
    buffer.seek(0)
    data = processor.process_pdf(buffer, "job_application")
    score, report = calculate_score(data, **params)
    application.score = score
    application.report = report
    application.save()
# Education module — pattern applied to all 5 modules
# Return of -1 signals automatic discard to the orchestrator

def _education_module(education: dict | None) -> tuple[float, dict]:
    detail = {"score": 0, "weight": EDUCATION_WEIGHT}
    if not education:
        return 0.0, detail
    level  = education.get("level", "")
    status = education.get("status", "")
    if level == "primary" or (level == "secondary" and status != "complete"):
        return -1.0, detail  # discard: cannot be compensated
    score = 100 if status == "complete" else 60
    return score * EDUCATION_WEIGHT, {**detail, "score": score}

def calculate_score(data: dict, **params) -> tuple[int, str]:
    item = data["items"][0]
    llm_score = item.get("final_score", 3)
    if item.get("discarded") or _discard_by_params(item, params):
        return 0, discard_report(item)
    score_map = {1: 10, 2: 30, 3: 50, 4: 70, 5: 90}
    base = score_map[llm_score]
    base += _customer_service_bonus(item.get("experiences", []))
    base += _fast_food_bonus(item.get("experiences", []))
    scale = _map_to_5_scale(min(100, base))
    return scale, generate_report(base, scale, item)
# Mass sending with isolated partial failures
# One failed message does NOT cancel the entire campaign

@shared_task
def send_mass_campaign(campaign_id: int):
    campaign = Campaign.objects.get(id=campaign_id)
    contacts = set(campaign.contacts.all())
    for label in campaign.labels.all():
        contacts.update(label.contacts.all())

    service = TwilioService()
    for contact in contacts:
        campaign.refresh_from_db()         # check real-time cancellation
        if campaign.status == "cancelled":
            return
        try:
            msg = service.send_template(contact, campaign.template)
            MessageLog.objects.create(
                campaign=campaign, contact=contact,
                twilio_sid=msg.sid, status=msg.status or "queued"
            )
            campaign.sent += 1
        except TwilioRestException as exc:
            MessageLog.objects.create(
                campaign=campaign, contact=contact, status="failed",
                error_code=str(exc.code), error_msg=str(exc.msg)
            )
            campaign.failed += 1
        campaign.save()
        time.sleep(0.1)  # respect Twilio rate limit: ~10 msg/s
    campaign.status = "completed"
    campaign.completed_at = timezone.now()
    campaign.save()

Error handling and resilience

LLM failure or timeoutCelery retries with exponential backoff (3 attempts); error status if exhausted
Unreadable or scanned PDFAutomatic OCR fallback; activated if extracted text is under 100 characters
WhatsApp message failsIndividual log in MessageLog; campaign continues; sent/failed counters updated
Template rejected by MetaRejected status + reason stored; re-submission available after correction
Template deletionDeletes in Twilio first; if it fails, blocks local deletion to preserve consistency
Twilio creation errormessages.error() in admin; record not persisted until API confirms success

Quantifiable results

Estimates based on HR Tech sector benchmarks in Latin America.

-94%
analysis time per CV
×8.8
CVs processed/day per analyst
100%
consistency in criteria
8:1
estimated year 1 ROI
MetricManual processWith AIImprovement
Analysis time per CV15 min< 45 sec-94%
CVs analyzed/day (1 analyst)32280+×8.8
Criteria consistency~70%100%+30 percentage points
Time to first response48–72 hours< 2 hours-96%
Cost per CV analyzed$12 USD$0.80 USD-93%
False positive rate (unfit candidates called)~35%< 8%-77%
ChannelEmail (previous)WhatsAppImprovement
Open rate22%87%+295%
Average read time4.2 hours8 minutes-97%
Response rate3%38%+1,167%

Interested in this type of solution?

I design and implement applied AI systems for real business processes: HR automation, document analysis, API integration, and omnichannel communication.