213 lines
9.0 KiB
Python
213 lines
9.0 KiB
Python
from datetime import timedelta
|
||
|
||
from django.core.management.base import BaseCommand
|
||
from django.utils import timezone
|
||
|
||
from core.models import JobPosting, JobSource
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = "Seed realistic demo sources and job postings for local MVP reviews."
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
"--reset",
|
||
action="store_true",
|
||
help="Delete existing JobSource and JobPosting records before seeding.",
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
if options["reset"]:
|
||
JobPosting.objects.all().delete()
|
||
JobSource.objects.all().delete()
|
||
self.stdout.write(self.style.WARNING("Existing sources and jobs deleted."))
|
||
|
||
today = timezone.localdate()
|
||
now = timezone.now()
|
||
|
||
sources_payload = [
|
||
{
|
||
"name": "France Travail – Dijon",
|
||
"family": JobSource.Family.PORTAL,
|
||
"url": "https://candidat.francetravail.fr/offres/recherche?lieux=21D",
|
||
"status": JobSource.Status.ACTIVE,
|
||
"owner": "Ops Team",
|
||
"notes": "Primary public feed for Dijon area listings.",
|
||
"last_checked_at": now - timedelta(minutes=22),
|
||
},
|
||
{
|
||
"name": "Apec Bourgogne-Franche-Comté",
|
||
"family": JobSource.Family.PORTAL,
|
||
"url": "https://www.apec.fr/candidat/recherche-emploi.html",
|
||
"status": JobSource.Status.ACTIVE,
|
||
"owner": "Ops Team",
|
||
"notes": "Executive and white-collar roles.",
|
||
"last_checked_at": now - timedelta(hours=2),
|
||
},
|
||
{
|
||
"name": "Adecco Dijon Tertiaire",
|
||
"family": JobSource.Family.AGENCY,
|
||
"url": "https://www.adecco.fr/offres-emploi/?k=dijon",
|
||
"status": JobSource.Status.PAUSED,
|
||
"owner": "Data Partner",
|
||
"notes": "Paused while waiting on extraction rule update.",
|
||
"last_checked_at": now - timedelta(days=2),
|
||
},
|
||
{
|
||
"name": "Urgo Group Careers",
|
||
"family": JobSource.Family.COMPANY,
|
||
"url": "https://careers.urgo-group.com/",
|
||
"status": JobSource.Status.ACTIVE,
|
||
"owner": "Ops Team",
|
||
"notes": "Direct company careers feed.",
|
||
"last_checked_at": now - timedelta(minutes=55),
|
||
},
|
||
{
|
||
"name": "SEB Selongey Careers",
|
||
"family": JobSource.Family.COMPANY,
|
||
"url": "https://www.groupe-seb.com/fr/carrieres",
|
||
"status": JobSource.Status.ERROR,
|
||
"owner": "Connector Squad",
|
||
"notes": "Blocked by anti-bot response, requires parser fallback.",
|
||
"last_checked_at": now - timedelta(days=5),
|
||
},
|
||
]
|
||
|
||
source_map = {}
|
||
for payload in sources_payload:
|
||
source, _ = JobSource.objects.update_or_create(url=payload["url"], defaults=payload)
|
||
source_map[source.name] = source
|
||
|
||
jobs_payload = [
|
||
{
|
||
"source": "France Travail – Dijon",
|
||
"title": "Développeur Python Django (H/F)",
|
||
"company": "Noveo Digital",
|
||
"location": "Dijon",
|
||
"contract_type": JobPosting.ContractType.CDI,
|
||
"remote": True,
|
||
"salary": "38k€–45k€",
|
||
"apply_url": "https://example.com/jobs/python-django-dijon",
|
||
"published_at": today - timedelta(days=1),
|
||
"description": "Concevoir des APIs Django et maintenir un back-office métier orienté data.",
|
||
"is_active": True,
|
||
"duplicate_score": 4.10,
|
||
},
|
||
{
|
||
"source": "France Travail – Dijon",
|
||
"title": "Intégrateur Front-end React",
|
||
"company": "Pixel Nordic",
|
||
"location": "Dijon",
|
||
"contract_type": JobPosting.ContractType.CDD,
|
||
"remote": False,
|
||
"salary": "34k€",
|
||
"apply_url": "https://example.com/jobs/react-integrator",
|
||
"published_at": today - timedelta(days=2),
|
||
"description": "Intégrer des interfaces performantes et accessibles au sein d'une équipe produit.",
|
||
"is_active": True,
|
||
"duplicate_score": 1.90,
|
||
},
|
||
{
|
||
"source": "Apec Bourgogne-Franche-Comté",
|
||
"title": "Product Owner CMS",
|
||
"company": "Cobalt Studio",
|
||
"location": "Dijon",
|
||
"contract_type": JobPosting.ContractType.CDI,
|
||
"remote": True,
|
||
"salary": "45k€–52k€",
|
||
"apply_url": "https://example.com/jobs/product-owner-cms",
|
||
"published_at": today - timedelta(days=3),
|
||
"description": "Piloter la roadmap d'une plateforme CMS B2B et animer les rituels produit.",
|
||
"is_active": True,
|
||
"duplicate_score": 0.50,
|
||
},
|
||
{
|
||
"source": "Adecco Dijon Tertiaire",
|
||
"title": "Technicien support applicatif",
|
||
"company": "Helix Services",
|
||
"location": "Chenôve",
|
||
"contract_type": JobPosting.ContractType.INTERIM,
|
||
"remote": False,
|
||
"salary": "13,50€/h",
|
||
"apply_url": "https://example.com/jobs/support-applicatif",
|
||
"published_at": today - timedelta(days=4),
|
||
"description": "Support N1/N2 sur une suite SaaS et suivi d'incidents applicatifs.",
|
||
"is_active": True,
|
||
"duplicate_score": 6.75,
|
||
},
|
||
{
|
||
"source": "Urgo Group Careers",
|
||
"title": "Data Analyst RH",
|
||
"company": "Urgo Group",
|
||
"location": "Chenôve",
|
||
"contract_type": JobPosting.ContractType.CDI,
|
||
"remote": True,
|
||
"salary": "40k€",
|
||
"apply_url": "https://example.com/jobs/data-analyst-rh",
|
||
"published_at": today - timedelta(days=5),
|
||
"description": "Structurer les tableaux de bord RH et fiabiliser les flux de reporting.",
|
||
"is_active": True,
|
||
"duplicate_score": 0.30,
|
||
},
|
||
{
|
||
"source": "SEB Selongey Careers",
|
||
"title": "Ingénieur QA Automatisation",
|
||
"company": "Groupe SEB",
|
||
"location": "Selongey",
|
||
"contract_type": JobPosting.ContractType.CDI,
|
||
"remote": False,
|
||
"salary": "42k€–48k€",
|
||
"apply_url": "https://example.com/jobs/qa-automation",
|
||
"published_at": today - timedelta(days=6),
|
||
"description": "Automatiser les scénarios de validation et renforcer la non-régression continue.",
|
||
"is_active": False,
|
||
"duplicate_score": 3.20,
|
||
},
|
||
{
|
||
"source": "Apec Bourgogne-Franche-Comté",
|
||
"title": "Chef de projet digital",
|
||
"company": "Mutualité Bourgogne",
|
||
"location": "Dijon",
|
||
"contract_type": JobPosting.ContractType.CDD,
|
||
"remote": False,
|
||
"salary": "39k€",
|
||
"apply_url": "https://example.com/jobs/chef-projet-digital",
|
||
"published_at": today - timedelta(days=7),
|
||
"description": "Coordonner la refonte d'outils internes et piloter les prestataires externes.",
|
||
"is_active": True,
|
||
"duplicate_score": 2.40,
|
||
},
|
||
{
|
||
"source": "France Travail – Dijon",
|
||
"title": "Développeur Full Stack Junior",
|
||
"company": "BFC Cloud",
|
||
"location": "Dijon",
|
||
"contract_type": JobPosting.ContractType.APPRENTICESHIP,
|
||
"remote": True,
|
||
"salary": "Selon grille alternance",
|
||
"apply_url": "https://example.com/jobs/fullstack-junior",
|
||
"published_at": today - timedelta(days=8),
|
||
"description": "Participer au développement d'un CMS métier avec Django et Vue.",
|
||
"is_active": True,
|
||
"duplicate_score": 5.60,
|
||
},
|
||
]
|
||
|
||
seeded_jobs = 0
|
||
for payload in jobs_payload:
|
||
source = source_map[payload.pop("source")]
|
||
_, created = JobPosting.objects.update_or_create(
|
||
source=source,
|
||
title=payload["title"],
|
||
company=payload["company"],
|
||
defaults=payload,
|
||
)
|
||
if created:
|
||
seeded_jobs += 1
|
||
|
||
self.stdout.write(
|
||
self.style.SUCCESS(
|
||
f"Demo seed complete: {len(source_map)} sources available, {JobPosting.objects.count()} total job postings ({seeded_jobs} new)."
|
||
)
|
||
)
|