Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,26 @@ services:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

App\Report\AiUsageFrequencyReport:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

App\Report\AiUsagePurposeReport:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

App\Report\AiPerceptionReport:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

App\Report\AiJobImpactReport:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

App\Report\AiJobMarketImpactReport:
parent: App\Report\AbstractReport
tags: [ 'barometre.report' ]

NumberFormatter:
class: \NumberFormatter
arguments: ['fr', '1']
51 changes: 51 additions & 0 deletions migrations/Version20260830082229.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Application\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20260830082229 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add new columns and ai_usage_purpose table for 2026 answers';
}

public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE ai_usage_purpose (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE response_aiusagepurpose (response_id INT NOT NULL, aiusagepurpose_id INT NOT NULL, INDEX IDX_8CAA88E7FBF32840 (response_id), INDEX IDX_8CAA88E74827D971 (aiusagepurpose_id), PRIMARY KEY(response_id, aiusagepurpose_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE response_aiusagepurpose ADD CONSTRAINT FK_8CAA88E7FBF32840 FOREIGN KEY (response_id) REFERENCES response (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE response_aiusagepurpose ADD CONSTRAINT FK_8CAA88E74827D971 FOREIGN KEY (aiusagepurpose_id) REFERENCES ai_usage_purpose (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE response ADD aiUsageFrequency INT DEFAULT NULL, ADD aiPerception INT DEFAULT NULL, ADD aiJobImpact INT DEFAULT NULL, ADD aiJobMarketImpact INT DEFAULT NULL');

$sqlAiUsagePurpose = 'INSERT INTO ai_usage_purpose (name) VALUE (:name)';

$aiUsagePurposes = [
'Debug / compréhension de code',
'Refactoring',
'Génération de boilerplate',
'Écriture de tests',
'Documentation',
'Rédaction de specs',
'Revue de code',
'Automatisation / CI',
];

foreach ($aiUsagePurposes as $aiUsagePurpose) {
$this->addSql($sqlAiUsagePurpose, ['name' => $aiUsagePurpose]);
}
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE response_aiusagepurpose DROP FOREIGN KEY FK_8CAA88E7FBF32840');
$this->addSql('ALTER TABLE response_aiusagepurpose DROP FOREIGN KEY FK_8CAA88E74827D971');
$this->addSql('DROP TABLE ai_usage_purpose');
$this->addSql('DROP TABLE response_aiusagepurpose');
$this->addSql('ALTER TABLE response DROP aiUsageFrequency, DROP aiPerception, DROP aiJobImpact, DROP aiJobMarketImpact');
}
}
Binary file added public/reports/2026/ai_job_market_impact.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/reports/2026/ai_perception.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/reports/2026/ai_usage_frequency.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/reports/2026/evolution_salaire.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions src/Campaign/Format/Formats/Format2026.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace App\Campaign\Format\Formats;

class Format2026 extends Format2025
{
public function getColumns()
{
return [
'', // Horodateur,
'gross_annual_salary', // Quelle est votre rémunération annuelle brute fixe en euro ?
'variable_annual_salary', // Quelle est votre rémunération annuelle brut variable (hors part fixe) en euro ?
'salary_satisfaction', // Estimez-vous votre rémunération satisfaisante ?
'salary_inflation', // Votre entreprise vous propose-t-elle à minima une réévaluation annuelle de votre rémunération du niveau de l’inflation ?
'initial_training', // Quelle est votre formation initiale ?
'retraining', // Venez-vous d'une reconversion ?
'status', // Quel est votre statut actuel ?
'job_title', // Quel est l'intitulé de votre poste actuel ?
'contract_work_duration', // Quelle est votre durée de travail contractuelle ?
'experience_in_year', // Quel est votre nombre d'année d'expérience total ?
'experience_in_current_job', // Quel est votre nombre d'année d'expérience dans votre entreprise actuelle ?
'leave_job', // Si vous avez cherché à changer de poste en cette année, est-ce que vous avez ?
'freelance_tjm', // Quelle est votre TJM en euro ?
'freelance_average_work_day', // Quel est votre nombre de jours travaillé en moyenne dans l’année ?
'company_department', // Quel est votre département de travail ?
'company_type', // Dans quel type d'entreprise travaillez-vous ?
'company_size', // Taille de votre entreprise
'job_interest', // Quels sont les aspects les plus importants dans votre emploi actuel ?
'company_origin', // Si elle n'est pas française, quelle est l'origine de votre entreprise ou maison mère ?
'discrimination_during_hiring', // Avez-vous le sentiment d'avoir été victime de discrimination à l'embauche ?
'other_language', // "Développez-vous dans d'autres langages que PHP ? Si oui, quel est le principal ?"
'remote_usage', // Travaillez-vous en télétravail ?
'remote_pace', // Quel serait le rythme idéal en télétravail pour vous ? (en nombre de jour par semaine)
'technological_watch', // Faites-vous régulièrement de la veille technique ?,
'os_developpment', // Sous quel OS développez-vous principalement ?,
'number_meetup_participation', // À combien d'événement / Meetup tech avez-vous participé sur la dernière année ?,
'community_inclusion', // Est-ce que vous pensez que l'AFUP, sa communauté et ses événements sont inclusifs ?
'speciality', // Quelle est votre plus grande spécialité ?,
'php_version', // Quelle version de PHP utilisez-vous au quotidien ?,
'php_strength', // "D'après vous, quelle est la plus grande force de PHP ?"
'has_formation', // Avez-vous suivi une formation au cours de ces deux dernières années ?,
'formation_subject', // Sur quel(s) sujet(s) avez-vous été formés ?,
'formation_impact', // Cela a-t-il eu une incidence sur votre emploi/rémunération ?,
'ai_usage_frequency', // Comment utilises-tu l'IA générative dans ton quotidien de dev ?
'ai_usage_purpose', // Pour quels usages principaux utilises-tu l'IA ?
'ai_perception', // Selon toi, l'IA dans le dev aujourd'hui, c'est plutôt…
'ai_job_impact', // Comment estimez-vous que l'IA impactera votre poste dans les 2 à 5 prochaines années ?
'ai_job_market_impact', // Selon vous, quel sera l'impact de l'IA sur le marché de l'emploi dans votre secteur ?
'include_ai_in_project', // Vos projets intègrent-ils des fonctionnalités liées à l'IA générative ?
'gender', // Vous êtes
'age', // Quel âge avez-vous ?
];
}
}
37 changes: 34 additions & 3 deletions src/Campaign/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
namespace App\Campaign;

use agallou\Departements\Collection as Departments;
use App\Entity\AiUsagePurpose;
use App\Entity\Campaign;
use App\Entity\Certification;
use App\Entity\ContainerEnvironmentUsage;
use App\Entity\HostingType;
use App\Entity\JobInterest;
use App\Entity\Response;
use App\Entity\Speciality;
use App\Enums\AiJobImpactEnums;
use App\Enums\AiJobMarketImpactEnums;
use App\Enums\AiPerceptionEnums;
use App\Enums\AiUsageFrequencyEnums;
use App\Enums\CmsUsageInProjectEnums;
use App\Enums\CompanySizeEnums;
use App\Enums\CompanyTypeEnums;
Expand Down Expand Up @@ -43,6 +48,7 @@
use App\Enums\StatusEnums;
use App\Enums\TechnologicalWatchEnums;
use App\Enums\WorkMethodEnums;
use App\Repository\AiUsagePurposeRepository;
use App\Repository\CertificationRepository;
use App\Repository\ContainerEnvironmentUsageRepository;
use App\Repository\HostingTypeRepository;
Expand All @@ -62,6 +68,7 @@ public function __construct(
private readonly HostingTypeRepository $hostingTypeRepository,
private readonly ContainerEnvironmentUsageRepository $containerEnvironmentUsageRepository,
private readonly JobInterestRepository $jobInterestRepository,
private readonly AiUsagePurposeRepository $aiUsagePurposeRepository,
private readonly PropertyAccessorInterface $propertyAccessor,
) {
}
Expand Down Expand Up @@ -128,6 +135,10 @@ public function createResponse(array $data, Campaign $campaign): Response
'Covid19SalaryImpact' => ['key' => 'covid19_salary_impact', 'class' => SalaryImpactEnums::class],
'Covid19PartialUnemployment' => ['key' => 'covid19_partial_unemployment', 'class' => PartialUnemploymentEnums::class],
'Covid19RegularRemoteFeeling' => ['key' => 'covid19_regular_remote_feeling', 'class' => RegularRemoteFeelingEnums::class],
'aiUsageFrequency' => ['key' => 'ai_usage_frequency', 'class' => AiUsageFrequencyEnums::class],
'aiPerception' => ['key' => 'ai_perception', 'class' => AiPerceptionEnums::class],
'aiJobImpact' => ['key' => 'ai_job_impact', 'class' => AiJobImpactEnums::class],
'aiJobMarketImpact' => ['key' => 'ai_job_market_impact', 'class' => AiJobMarketImpactEnums::class],
];

foreach ($enumValues as $field => $enum) {
Expand Down Expand Up @@ -194,14 +205,21 @@ public function createResponse(array $data, Campaign $campaign): Response
'oui' === mb_strtolower($data['formation_impact'] ?? '')
);

$response->setUseGenerativeAI(
'oui' === mb_strtolower($data['use_generative_ai'] ?? '')
);
if (isset($data['use_generative_ai'])) {
$response->setUseGenerativeAI(
'oui' === mb_strtolower($data['use_generative_ai'])
);
}

$response->setIncludeAiInProject(
'oui' === mb_strtolower($data['include_ai_in_project'] ?? '')
);

$this->addAiUsagePurpose(
$response,
explode(', ', $data['ai_usage_purpose'] ?? '')
);

return $response;
}

Expand Down Expand Up @@ -261,6 +279,19 @@ private function addContainerEnvironmentUsage(Response $response, array $contain
}
}

private function addAiUsagePurpose(Response $response, array $aiUsagePurposes)
{
foreach ($aiUsagePurposes as $name) {
$aiUsagePurpose = $this->aiUsagePurposeRepository->findOneBy(['name' => mb_trim($name)]);

if (!$aiUsagePurpose instanceof AiUsagePurpose) {
continue;
}

$response->addAiUsagePurpose($aiUsagePurpose);
}
}

private function addJobInterest(Response $response, array $jobInterests)
{
foreach ($jobInterests as $name) {
Expand Down
43 changes: 43 additions & 0 deletions src/Entity/AiUsagePurpose.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\AiUsagePurposeRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: AiUsagePurposeRepository::class)]
#[ORM\Table(name: 'ai_usage_purpose')]
class AiUsagePurpose
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue]
protected int $id;

#[ORM\Column(type: 'string', length: 100)]
protected string $name;

public function getId(): int
{
return $this->id;
}

public function setName(string $name): self
{
$this->name = $name;

return $this;
}

public function getName(): string
{
return $this->name;
}

public function __toString(): string
{
return $this->name;
}
}
84 changes: 84 additions & 0 deletions src/Entity/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,30 @@ class Response
#[ORM\Column(type: 'boolean', nullable: true)]
private ?bool $includeAiInProject = null;

#[ORM\Column(type: 'integer', nullable: true)]
private ?int $aiUsageFrequency = null;

/** @var Collection<int, AiUsagePurpose> */
#[ORM\ManyToMany(targetEntity: AiUsagePurpose::class)]
private Collection $aiUsagePurposes;

#[ORM\Column(type: 'integer', nullable: true)]
private ?int $aiPerception = null;

#[ORM\Column(type: 'integer', nullable: true)]
private ?int $aiJobImpact = null;

#[ORM\Column(type: 'integer', nullable: true)]
private ?int $aiJobMarketImpact = null;

public function __construct()
{
$this->certifications = new ArrayCollection();
$this->specialities = new ArrayCollection();
$this->containerEnvironmentsUsage = new ArrayCollection();
$this->hostingTypes = new ArrayCollection();
$this->jobInterests = new ArrayCollection();
$this->aiUsagePurposes = new ArrayCollection();
}

public function getId(): int
Expand Down Expand Up @@ -931,4 +948,71 @@ public function setIncludeAiInProject(?bool $includeAiInProject): self

return $this;
}

public function getAiUsageFrequency(): ?int
{
return $this->aiUsageFrequency;
}

public function setAiUsageFrequency(?int $aiUsageFrequency): self
{
$this->aiUsageFrequency = $aiUsageFrequency;

return $this;
}

public function getAiUsagePurposes(): Collection
{
return $this->aiUsagePurposes;
}

public function setAiUsagePurposes(Collection $aiUsagePurposes): self
{
$this->aiUsagePurposes = $aiUsagePurposes;

return $this;
}

public function addAiUsagePurpose(AiUsagePurpose $aiUsagePurpose): self
{
$this->aiUsagePurposes->add($aiUsagePurpose);

return $this;
}

public function getAiPerception(): ?int
{
return $this->aiPerception;
}

public function setAiPerception(?int $aiPerception): self
{
$this->aiPerception = $aiPerception;

return $this;
}

public function getAiJobImpact(): ?int
{
return $this->aiJobImpact;
}

public function setAiJobImpact(?int $aiJobImpact): self
{
$this->aiJobImpact = $aiJobImpact;

return $this;
}

public function getAiJobMarketImpact(): ?int
{
return $this->aiJobMarketImpact;
}

public function setAiJobMarketImpact(?int $aiJobMarketImpact): self
{
$this->aiJobMarketImpact = $aiJobMarketImpact;

return $this;
}
}
23 changes: 23 additions & 0 deletions src/Enums/AiJobImpactEnums.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace App\Enums;

class AiJobImpactEnums extends AbstractEnums
{
public const AUCUN_IMPACT_SIGNIFICATIF = 1;
public const IMPACT_POSITIF = 2;
public const TRANSFORMATION_DU_POSTE = 3;
public const RISQUE_DE_REDUCTION_OU_SUPPRESSION = 4;
public const INCERTITUDE = 5;

protected array $choices = [
self::AUTRE => 'Autre',
self::AUCUN_IMPACT_SIGNIFICATIF => 'Aucun impact significatif',
self::IMPACT_POSITIF => 'Impact positif (gain d\'efficacité, montée en compétence…)',
self::TRANSFORMATION_DU_POSTE => 'Transformation du poste (évolution des missions)',
self::RISQUE_DE_REDUCTION_OU_SUPPRESSION => 'Risque de réduction ou suppression du poste',
self::INCERTITUDE => 'Incertitude / difficile à évaluer',
];
}
Loading
Loading