Dépendances et sources
Trois conversions et un référentiel commun en CHF et en jours. Ne remplace pas la recherche : fixe les champs, les conversions, la forme du livrable.
| Élément | Détail |
|---|---|
| Références | 5 fichiers · benchmarks-marche-ch.md, cadre-juridique.md, calculs.md, negociation-remuneration.md, referentiel-champs.md |
| Gabarits | 2 fichiers · comparatif-contrats.md, contrats-exemple.json |
| Scripts | 1 fichiers · normaliser.py |
| Serveur MCP | swiss-caselaw · https://mcp.opencaselaw.ch/mcp · lecture seule, sans clé, outils mcp__swiss_caselaw__* |
| Outils appelés | check_claim_supportcitefind_relevant_erwaegungget_law |
Sources externes mobilisées
| Source | Ce qu'on y trouve |
|---|---|
| CO | art. 319 ss, 322 ss, 329 ss, 335 ss |
| LPP | seuils, bonifications, part patronale |
| LAA / LACI / AVS | plafonds et taux annuels |
| CCT applicables | salaires minimaux, 13e, vacances |
Index des fichiers
Cliquer pour ouvrir. La touche / met le focus dans la recherche.
- SKILL.md
SKILL.md344 l. - référence
references/benchmarks-marche-ch.md74 l. - référence
references/cadre-juridique.md162 l. - référence
references/calculs.md253 l. - référence
references/negociation-remuneration.md141 l. - référence
references/referentiel-champs.md146 l. - gabarit
templates/comparatif-contrats.md206 l. - gabarit
templates/contrats-exemple.json83 l. - script
scripts/normaliser.py480 l.
contrats-travail-ch/SKILL.md
19 Ko · 344 lignes
Contrats de travail suisses — analyse et comparatif normalisé
Deux contrats de travail ne se comparent jamais sur le salaire mensuel. Un 8'500 × 13 à 42 h avec 4 semaines de vacances peut valoir moins qu'un 7'800 × 12 à 40 h avec 5 semaines et une meilleure caisse de pension. Cette skill convertit chaque contrat dans un référentiel commun en CHF et en jours, teste sa validité au regard du droit impératif, puis produit un comparatif qui dit où est l'écart et à partir de quel montant l'offre « moins bien payée » devient meilleure.
Le droit matériel se cherche avec droit-suisse (norme → jurisprudence → doctrine, et
cite() sur chaque référence). Cette skill ne remplace pas la recherche : elle fixe les
champs à extraire, les conversions à faire et la forme du livrable.
When to Use / Quand l'utiliser
- Comparer 2 à 5 offres ou contrats (CDI, CDD, temps partiel, fonctions différentes).
- Auditer un contrat unique avant signature : ce qui est sous le minimum légal, ce qui est flou, ce qu'il faut faire préciser par écrit.
- Vérifier la cohérence d'un package (salaire + bonus + prévoyance + vacances + frais).
- Préparer une négociation : chiffrer ce qu'on demande et ce qu'on est prêt à céder, et
l'appuyer sur l'économie de l'employeur (
references/negociation-remuneration.md).
Hors périmètre : rédiger un contrat ; calculer un net fiscal exact (dépend du canton, de l'état civil, des charges) ; se substituer à un avocat pour un litige (licenciement abusif, contestation LPP).
Dépendances — chaînage obligatoire
Cette skill fixe les champs à extraire, les conversions et la forme du
livrable. Elle ne tranche pas le droit : dès qu'une clause doit être qualifiée de nulle,
qu'une exception au minimum impératif est invoquée ou qu'un arrêt fait autorité, la
réponse se construit avec droit-suisse (phases Sources et Analyse).
- Validité d'une clause, minimum impératif, état de la jurisprudence → skill
droit-suisse references/cadre-juridique.md- Formes de citation →
droit-suisse/references/citation-suisse.md(source unique) - Checklist de sortie commune →
droit-suisse/references/checklist-sortie.md - Vérification des affirmations décisives (nullité, seuil LPP, plafond AC) →
cite()puischeck_claim_support()
Principe : trois conversions, rien d'autre
Tout le comparatif tient dans trois opérations. Si une ligne du livrable ne fait pas l'une des trois, elle est du décor.
- Temps → année. Mensuel × nombre de mensualités, horaire hebdo × 52, vacances en semaines converties en heures, bonus annualisé et pondéré par sa probabilité.
- Avantage → CHF. Chaque écart non monétaire (1 semaine de vacances, 2 jours de home office, 60 % vs 50 % de part patronale LPP, 80 % vs 100 % en cas de maladie) reçoit une valeur en francs par an.
- Clause → droit. Chaque clause est confrontée au minimum impératif : ce qui est nul ne se compare pas, ça se signale et ça se renégocie.
Étape 0 — Cadrer la référence (avant de lire les contrats)
Fixer et écrire en tête du livrable, car tout le reste en dépend :
| Paramètre | Pourquoi il change le résultat |
|---|---|
| Année de référence | seuils LPP, plafond AC, taux AVS changent chaque année |
| Canton de l'établissement | jours fériés, allocations familiales, impôt à la source |
| Âge du travailleur | bonification LPP minimale (7/10/15/18 %), 5e semaine avant 20 ans |
| Taux d'activité visé | comparaison à 100 % équivalent puis au taux réel |
| CCT / CCNT applicable | salaires minimaux, 13e, vacances, prévoyance meilleurs que la loi |
| Statut de séjour | permis B/C/L, frontalier, impôt à la source |
| Horizon | 1 an (cash) ou 10 ans (prévoyance, carrière) |
Un seul de ces paramètres non fixé et le comparatif est faux. Les autres hypothèses s'écrivent explicitement, elles ne se devinent pas.
Étape 1 — Extraire sans interpréter
Les contrats suisses arrivent souvent en scan sans couche texte. pypdf renvoie alors 0
caractère : ne pas conclure que le PDF est vide. Deux voies, dans cet ordre :
tesseractavec le pack français (apt-get install tesseract-ocr tesseract-ocr-fra), rendu préalable des pages à 300 dpi avec PyMuPDF (page.get_pixmap(dpi=300)), puistesseract page.png - -l fra --psm 6. Fiable, local, rapide sur 1 à 6 pages.vision_analyzeen dernier recours : le backend expire régulièrement sur des pages A4 complètes (timeouts répétés), et une extraction vision se trompe sur les chiffres d'un devis ou d'un contrat. Ne jamais publier un montant lu par vision sans le recouper avec un décompte de salaire ou un certificat.
Un même document est souvent dupliqué dans plusieurs dossiers du drive (banque, ORP, locataire) : chercher le contrat dans les dossiers de postulation, pas seulement dans le dossier de l'employeur.
Un fichier de données par contrat, une valeur par champ, et trois états distincts :
valeur: le contrat prévoit quelque chose.null/"non prévu": le contrat est silencieux (ce n'est pas zéro, c'est un défaut légal à appliquer ou un point à clarifier)."à vérifier": le document ne permet pas de conclure (avenant manquant, renvoi à un règlement non fourni, caisse de pension inconnue).
Les 12 blocs du référentiel (détail des champs : references/referentiel-champs.md) :
| # | Bloc | Ce qu'on y met |
|---|---|---|
| A | Cadre | parties, type, canton, CCT, fonction, taux, lieu, télétravail |
| B | Fixe | salaire de base, mensualités, 13e, allocations, indexation |
| C | Variable | bonus cible/max/garanti, commission, actions, conditions de versement |
| D | Frais | forfaits, remboursement réel, voiture, laptop, téléphone, formation |
| E | Temps | horaire, annualisation, heures sup, astreintes, pauses |
| F | Absences | vacances, jours fériés, maladie, accident, maternité/paternité, proche aidant |
| G | Prévoyance | LPP (caisse, plan, bonification, part patronale), LAA, IJM, 3e pilier |
| H | Fin | durée, essai, délai de congé, protection, certificat |
| I | Clauses | non-concurrence, exclusivité, confidentialité, PI |
| J | Discipline | règlement, sanctions, contrôles |
| K | Carrière | évaluation, formation, promotion, mobilité |
| L | Conformité | permis, cotisations, requalification, égalité |
Étape 2 — Tester la validité avant de comparer
Un contrat qui viole le droit impératif n'est pas « moins bon » : il est partiellement nul (CO 361), et la clause est remplacée par le minimum légal (CO 362 pour les exceptions). Comparer un 3 % de cotisation LPP « en lieu et place » avec un vrai plan LPP, c'est comparer une clause nulle avec une prestation.
Contrôles obligatoires (tableau complet et articles : references/cadre-juridique.md) :
- vacances < 4 semaines (5 jusqu'à 20 ans révolus) ;
- délai de congé < 1 mois après l'essai, essai > 3 mois ;
- bonification LPP sous le minimum d'âge, part patronale < part salariale, salarié non affilié alors que le seuil est dépassé ;
- salaire sous le minimum de la CCT étendue, si elle s'applique ;
- non-concurrence non écrite, > 3 ans, ou sans contrepartie identifiable ;
- heures supplémentaires imposées sans supplément ni compensation alors que la renonciation écrite manque ;
- assurance perte de gain maladie < 80 % / 720 jours sans repli CO 324a ;
- requalification : « indépendant » ou « mandat » avec subordination (horaires, lieu, intégration, outils) → cotisations AVS/LPP/LAA rétroactives.
Sortie de l'étape : une liste de points bloquants (nul, à corriger) et de points flous (à clarifier par écrit avant signature).
Étape 3 — Normaliser
Formules, valeurs 2026 et exemple chiffré : references/calculs.md. Les cinq grandeurs à
produire pour chaque contrat :
| Grandeur | Définition |
|---|---|
brut_annuel |
fixe × mensualités + bonus espéré + autres rémunérations soumises |
cout_employeur |
brut + cotisations patronales + LPP patronale + LAA + AF + frais |
cash_annuel |
brut + frais forfaitaires non soumis (ce qui tombe sur le compte) |
taux_horaire |
brut / heures effectivement travaillées (vacances et fériés déduits) |
prevoyance_annuelle |
part patronale LPP + valeur de la couverture maladie/accident |
Puis trois valeurs dérivées qui font basculer les décisions :
- valeur d'un jour libre = brut annuel / jours ouvrables (260 − fériés) ;
- package complet = cash + part patronale LPP + heures non travaillées valorisées au taux horaire de la référence ;
- seuil de bascule = combien B devrait payer en plus par an pour égaler A sur ce package.
Le script scripts/normaliser.py fait ce calcul sur un JSON de contrats et sort le tableau
markdown + les packages. L'utiliser plutôt que recalculer à la main : il trace les hypothèses.
python3 scripts/normaliser.py contrats.json --reference A --md comparatif.md --json calculs.json
Contrats d'années différentes (historique, progression)
Comparer des postes successifs (2014, 2022, 2026) impose deux réglages par contrat, sinon le comparatif est faux :
"parametres": {"annee": 2014, "lpp_seuil": 21150, "lpp_deduction": 24675, "ac_plafond": 126000},
"facteur_indexation": 1.0715
parametres: seuils LPP, plafond AC et taux cantonaux de l'année du contrat (sinon on applique les seuils 2026 à un salaire 2014).facteur_indexation: IPC de l'année du contrat vers l'année de référence, appliqué à toutes les grandeurs monétaires en sortie. IPC OFS base déc. 2010 = 100, moyennes annuelles : 2014 = 99.0, 2022 = 101.9, 2025 = 105.4, 2026 = 106.1 (janvier-octobre). Facteur = IPC_{2026} / IPC_{année}.lpp.cotisation_totale: quand le certificat de salaire donne le montant réel, il prime sur le calcul théorique (les caisses publiques cotisent souvent bien au-dessus du minimum légal).
Deux pièges vérifiés sur ce cas : salaire_mensuel doit être le montant réellement versé au
taux d'activité prévu (pas le 100 %), et horaire_hebdo l'horaire plein temps (le script
applique le taux). Sinon le taux horaire est faux du facteur d'activité.
Reconstituer le 100 % avant de comparer
Quand il n'y a qu'un certificat de salaire d'une année incomplète ou à taux variable, le
100 % se recalcule (references/calculs.md, §9) puis se recoupe sur une deuxième pièce :
un autre certificat, un décompte de salaire, ou le contrat. Un chiffre reconstitué qui n'est
pas recoupé est une hypothèse, pas un salaire.
Trois signaux d'erreur :
- un écart implicite de plus de 5 %/an entre deux certificats (le calendrier des taux est faux) ;
- un brut de certificat supérieur au
M × 13du contrat (bonus ou augmentation en cours d'année) ; - un certificat de travail qui contredit le contrat sur le taux d'activité (le décompte tranche).
Le résultat se publie en fourchette (« 112'800 à 113'800 »), jamais en chiffre unique.
Étape 4 — Comparer
Quatre lectures, dans cet ordre :
- Écart brut vs contrat de référence, ligne par ligne, en CHF/an et en %. On regarde les écarts, pas un score.
- Deux packages en CHF, formules affichées : le cash (ce qui tombe sur le compte) et le package complet (cash + prévoyance patronale + heures non travaillées valorisées au taux horaire de la référence). Pas de score 0-100 : un indice composite cache les arbitrages, un montant en francs les montre.
- Seuil de bascule : « B devrait payer 3'954 CHF de plus par an pour égaler A sur le package complet ». C'est la phrase qui sert en négociation.
- Positionnement marché : la comparaison entre contrats ne dit pas si le mieux est bon.
Ajouter la bande de référence du niveau réel (pas de l'intitulé), corrigée du canton, avec
source et année :
references/benchmarks-marche-ch.md. Un écart de plus de 15 % sous la bande se nomme.
Les périodes dégradées (stage, formation, ramp-up) se sortent du comparatif et se chiffrent à part comme coût d'entrée : comparer les régimes de croisière, puis dire combien coûte l'année de transition. Mélanger les deux fausse le taux horaire et le seuil de bascule.
La sécurité et les conditions ne se monétisent pas : elles restent qualitatives, lisibles dans le tableau, et un contrat avec clause bloquante est hors classement quel que soit son package. Interdit : un « gagnant » unique sans formule ni pondération affichée.
Étape 5 — Rendre
Gabarit : templates/comparatif-contrats.md. Deux niveaux, toujours :
- Synthèse en tête : 5 lignes maximum, le verdict, l'écart en CHF, ce qui fait basculer.
- Sections thématiques autonomes : rémunération, prévoyance, temps, sécurité, clauses. Chacune se lit seule et se termine par « ce qu'il faut demander ».
Plus trois annexes : données extraites (avec les à vérifier), points juridiques, sources.
Le tableau de conditions est obligatoire, pas seulement les montants : 15 à 25 lignes
(type, durée, essai, fonction, taux, horaire, mensualités, variable, vacances, fériés,
télétravail, LPP, LAA, IJM, maladie, heures sup, préavis, non-concurrence, frais, 336c).
C'est lui qui permet de juger un contrat qu'aucun chiffre ne résume. Les conditions qui
vivent dans un document non fourni (CGE, règlement du personnel, plan LPP) se marquent
non documenté : jamais d'hypothèse silencieuse dans cette colonne.
Règles dures
- Jamais un mensuel contre un mensuel. Annualiser, toujours, en affichant le nombre de mensualités.
- Le 13e n'est pas un bonus. C'est du fixe annualisé. Le bonus est variable, donc pondéré par sa probabilité et présenté séparément (garanti / cible / max). Un contrat à 13 mensualités face à un contrat à 12 : écrire « X × 13 » ou « Y en 12 fois », jamais « 6'300/mois » contre « 80'000/an » — l'erreur de lecture est de 8.3 %.
- « Non prévu » ≠ 0. Le silence du contrat déclenche le droit supplétif ou impératif, il ne vaut pas renonciation.
- Les frais ne sont pas du salaire, sauf forfait manifestement disproportionné ou non reconnu par l'AVS : dans ce cas, requalification et cotisations.
- La part patronale LPP est de la rémunération différée. Elle entre dans le coût employeur et dans le package complet, jamais dans le cash disponible.
- Un jour de vacances a un prix. Le chiffrer en CHF, sinon la 5e semaine ne pèse rien dans la comparaison.
- 80 % / 720 jours ≠ 100 % / 3 semaines. Comparer sur 12 mois d'incapacité, avec la carence, pas sur l'intitulé de la police.
- Le taux d'activité fausse tout. Comparer à 100 % équivalent d'abord, puis au taux réel : un 80 % à 6'400 peut battre un 100 % à 7'200 en taux horaire.
- Pas de net fiscal inventé. Le net avant impôt se calcule (cotisations déduites) ; l'impôt dépend du canton, du statut et de la situation familiale : hors périmètre, à annoncer.
- Chaque minimum cité est vérifié (
get_lawà l'année de référence,cite()pour la jurisprudence). Un chiffre de mémoire dans un comparatif est une faute. - Un poste passé se valorise à son salaire de sortie, pas à celui d'entrée : c'est le niveau atteint, et le seul comparable à une offre actuelle.
- Aucun chiffre reconstitué sans recoupement. Un salaire 100 % déduit d'un certificat partiel se vérifie sur une deuxième pièce, sinon il se présente comme une hypothèse.
- Aucun benchmark externe sans source. Fourchette de marché, marge sectorielle, revenu par tête : source + année, sinon le chiffre s'annonce comme une estimation. Préférer un chiffre dérivé des données de l'employeur (CA / effectif) à une moyenne non sourcée : c'est vérifiable, et ça ne peut pas être contesté en réunion.
- Un seul livrable. Comparatif et note de négociation vont dans le même document, avec des sections. Deux notes séparées obligent à recouper à la main et divergent à la première correction.
Pièges
- Comparer des packages sans regarder la déduction de coordination : deux salaires égaux peuvent avoir des salaires assurés très différents.
- Prendre le taux de bonification total pour la part patronale (souvent 50 à 70 %).
- Oublier que le salaire assuré LPP plafonne (3 × seuil d'entrée) alors que le plan peut assurer davantage : le contrat doit dire lequel.
- Confondre heures supplémentaires (CO 321c, dépassement de l'horaire contractuel) et heures de travail supplémentaires (LTr, dépassement du maximum légal) : les majorations et les bases ne sont pas les mêmes.
- Traiter un CDD comme un CDI : pas de délai de congé, pas de protection contre la résiliation, et le renouvellement en chaîne peut être abusif.
- Comparer des vacances en jours ouvrés avec des vacances en jours calendaires.
- Ignorer la clause d'annualisation du temps de travail : elle déplace les heures sup dans un compte épargne temps, donc hors salaire.
- Lire un « 90 % LAA » sans vérifier qui paie la prime complémentaire.
- Additionner des avantages en nature à leur valeur perçue (voiture de fonction) au lieu de leur coût réel et de leur part imposable.
- Comparer un contrat soumis à une CCT étendue avec un contrat hors CCT sans vérifier le champ d'application (secteur, canton, activité effective).
Checklist
- [ ] Année, canton, âge, taux d'activité, CCT et statut fixés en tête
- [ ] Chaque contrat extrait dans le référentiel,
non prévudistingué de0 - [ ] Contrôles de validité faits, points bloquants listés
- [ ] Brut annuel, coût employeur, cash, taux horaire, prévoyance calculés
- [ ] Tout salaire 100 % reconstitué recoupé sur une deuxième pièce, publié en fourchette
- [ ] Périodes de stage/formation sorties du comparatif et chiffrées à part
- [ ] Positionnement marché chiffré (bande, source, année, correction cantonale)
- [ ] Valeur du jour libre, package complet et seuil de bascule chiffrés
- [ ] Sécurité et conditions traitées à part, contrats bloquants hors classement
- [ ] Tableau de conditions livré (15-25 lignes),
non documentélà où le règlement manque - [ ] Un seul livrable consolidé si la demande inclut la négociation
- [ ] Chaque minimum légal cité avec son article vérifié à l'année de référence
- [ ] Livrable en 2 niveaux + annexes (données, points juridiques, sources)
- [ ] Limites annoncées : net fiscal, jurisprudence cantonale non publiée, règlements non fournis
Checklist commune (question, date du droit, sources, soutien, limites) :
droit-suisse/references/checklist-sortie.md.
Fichiers
| Fichier | Contenu |
|---|---|
references/referentiel-champs.md |
les champs du référentiel (12 blocs), ce qu'ils changent, comment les normaliser |
references/cadre-juridique.md |
minima impératifs, articles, valeurs 2026, requalification |
references/calculs.md |
formules, exemple chiffré, reconstitution d'un 100 %, cas limites |
references/benchmarks-marche-ch.md |
bandes de salaire suisses 2026, sources, correction cantonale |
references/negociation-remuneration.md |
modèle économique de l'employeur, ratios, package, ordre de la demande |
templates/comparatif-contrats.md |
gabarit du livrable |
templates/contrats-exemple.json |
jeu de données minimal, à copier |
scripts/normaliser.py |
calcul + tableau markdown + packages + contrôles de validité |
contrats-travail-ch/references/benchmarks-marche-ch.md
3 Ko · 74 lignes
Situer une offre sur le marché suisse
Un comparatif entre contrats dit lequel est le mieux des deux. Il ne dit pas si le mieux est bon. Le benchmark répond à « 80'000 pour ce poste, cette expérience, ce canton : c'est correct ? ». C'est la question qui suit toujours le comparatif, et elle se traite à part.
1. Qualifier le niveau réel, pas l'intitulé
Un contrat qui écrit « junior » ne rend pas junior quelqu'un qui a dix ans d'ingénierie ; un titre « senior » dans une PME de dix personnes ne vaut pas un senior dans une banque. Le niveau se déduit de : années d'expérience pertinentes, diplôme ou absence de diplôme, secteur, taille d'entreprise, responsabilité réelle (budget, équipe, architecture).
Bandes de référence : junior 0-3 ans, confirmé 3-6, senior 7+.
2. Corriger le canton
Les grilles publiées sont nationales et tirées par Zurich. Ordres de grandeur : Zurich +10 à +15 % sur la moyenne, Genève +5 à +10, arc lémanique proche de la moyenne, Romandie périphérique et cantons comme Fribourg, Neuchâtel, Jura −8 à −12, zones rurales −10 à −15. Une bande nationale appliquée telle quelle à une PME fribourgeoise surestime de ~10 %.
3. Où chercher, dans cet ordre
| Source | Ce qu'elle donne | Limite |
|---|---|---|
fed-group.ch (grille ingénierie) |
junior / confirmé / senior par rôle, national, hors bonus | grille commerciale, bandes indicatives |
upreer.ch (agrège salary.ch, OFS ESS, jobs.ch) |
médianes par secteur et par niveau, mentions Romandie | site de courtage, données agrégées |
| Payscale | percentiles déclaratifs | très bruité hors médiane |
| OFS / salarium | référence officielle par profession et canton | lent, mailles larges |
| jobs.ch / jobup | annonces réelles, ce que le marché demande | salaire rarement affiché |
| Rapport d'impact 42 Switzerland | premiers salaires des diplômés 42 | enquête déclarative, population hétérogène |
Croiser au moins deux sources. Une seule source ne fait pas un benchmark, elle fait une citation.
4. Instantané 2026 (à revérifier chaque année)
Salaire brut annuel, hors bonus, Suisse :
| Rôle | Junior | Confirmé | Senior |
|---|---|---|---|
| Data engineer | 90'000 – 105'000 | 105'000 – 135'000 | 135'000 – 160'000 |
| Software engineer | 85'000 – 100'000 | 100'000 – 130'000 | 130'000 – 160'000 |
| ML engineer | 95'000 – 115'000 | 115'000 – 140'000 | 140'000 – 160'000 |
| Data scientist | 90'000 – 110'000 | 110'000 – 135'000 | 135'000 – 155'000 |
Déclinaisons utiles (data engineer, Romandie) : médiane junior ~95'000 ; industrie / PME 82'000 – 100'000 ; retail / assurance 88'000 – 108'000 ; tech / SaaS 92'000 – 115'000 ; banque 98'000 – 122'000. Écart finance vs industrie locale : ~30 %.
Sortie d'école 42 (Suisse, enquête interne) : 41 % des premiers emplois entre 60'000 et 70'000, 25 % entre 70'000 et 85'000, 16 % au-dessus de 85'000. Utile pour un profil sans diplôme formel, à ne pas appliquer à un profil expérimenté.
5. Règles de restitution
- Comparer au niveau d'expérience, pas à l'intitulé du poste. C'est l'erreur qui fait accepter 20 % sous le marché : l'employeur paie « junior », le candidat est confirmé.
- Toujours fourchette + source + année. Une moyenne sans source n'est pas une preuve.
- Nommer le positionnement : « 15 % sous la bande junior », « dans le bas de la fourchette PME romande ». Un chiffre seul ne se négocie pas.
- Distinguer le marché du poste et le marché de la personne. Les deux existent ; le livrable doit dire lequel il utilise.
- Un écart > 15 % sous la bande se nomme. Ne pas l'adoucir en « marge de négociation » : c'est un signal, pas une nuance.
- Ne pas conclure d'une bande nationale sans correction régionale.
6. Place dans le livrable
Une section « Références de marché » de trois à cinq lignes : la bande retenue, sa source, le positionnement de l'offre, et la phrase de conséquence. Pas un tableau de dix sources : le lecteur veut savoir où il se situe, pas lire une bibliographie.
contrats-travail-ch/references/cadre-juridique.md
8 Ko · 162 lignes
Cadre juridique — ce qui est impératif, ce qui se négocie
Ce fichier ne remplace pas la recherche : il dit quoi vérifier et pourquoi un écart
change la comparaison. Chaque règle citée doit être relue dans sa version applicable à
l'année de référence (get_law avec as_of), puis vérifiée par cite() avant d'être
reprise dans un livrable.
1. Hiérarchie et clauses nulles
Ordre de force, du plus fort au plus faible :
- droit impératif fédéral (CO, LTr, LPP, LAA, LAFam, LEg) ;
- CCT étendue, puis CCT applicable, puis convention d'entreprise ;
- contrat individuel ;
- usage et directives de l'employeur.
CO 361 : les clauses qui dérogent au détriment du travailleur sont nulles, sauf les exceptions de CO 362. Conséquence pratique : une clause nulle est remplacée par le minimum légal, elle ne se compare pas. Un contrat qui prévoit 3 semaines de vacances n'est pas un contrat « moins généreux », c'est un contrat qui vaut 4 semaines.
2. Minima et règles impératives
| Domaine | Règle | Base | Dérogeable ? |
|---|---|---|---|
| Salaire | pas de minimum fédéral ; minimum CCT ou cantonal si applicable | CO 322 | oui, vers le haut |
| Paiement | à la fin du mois, sauf convention | CO 323 | oui, aménagement |
| Vacances | 4 semaines ; 5 jusqu'à 20 ans révolus | CO 329a | non, vers le bas |
| Jours fériés | au moins ceux du canton, payés | CO 329b, droit cantonal | non |
| Repos | 1 jour par semaine minimum | CO 329, LTr | non |
| Durée du travail | 45 h (industrie, bureau, technique, vente) ou 50 h | LTr 9 | non, vers le haut |
| Heures supplémentaires | +25 % ou compensation en temps | CO 321c | renonciation écrite possible, encadrée |
| Maladie | 3 semaines la 1re année, 3 mois ensuite, à 100 % | CO 324a | remplaçable par une assurance équivalente (80 % / 720 jours, al. 4) |
| Accident | LAA obligatoire, 80 % du salaire | LAA | complément contractuel possible |
| Maternité | 14 semaines, APG 80 % plafonné | CO 329f | non |
| Paternité | 2 semaines, APG 80 % plafonné | CO 329g | non |
| Congé jeunesse | 5 jours jusqu'à 30 ans, activités extrascolaires | CO (vérifier l'article) | non |
| Essai | 1 mois, 3 mois maximum par écrit ; préavis 7 jours | CO 335b | oui, dans la limite |
| Délai de congé | 1 mois (1re année), 2 mois (2e à 9e), 3 mois (dès 10e) | CO 335a | oui, mais jamais < 1 mois |
| Protection en cas de congé | 30 / 90 / 180 jours (1re année / 2e à 6e / dès 7e) | CO 336c | non |
| Certificat | droit à un certificat véridique | CO 330a | non |
| Non-concurrence | écrite, 3 ans maximum, intérêt légitime, contrepartie | CO 340-340c | oui, dans les limites |
| LPP | affiliation dès le seuil, bonification minimale, part patronale ≥ 50 % | LPP 2, 7, 16, 66 | non |
| Prescription du salaire | 5 ans | CO 128 ch. 3 | non |
Point d'attention : la protection CO 336c et le délai de congé CO 335a n'ont pas les mêmes paliers d'ancienneté. Les confondre est l'erreur la plus fréquente dans les comparatifs « sécurité ».
3. Prévoyance professionnelle — la mécanique à contrôler
Quatre chiffres déterminent tout, et deux contrats au même salaire peuvent diverger de plusieurs milliers de francs par an :
- Seuil d'entrée : salaire annuel AVS au-delà duquel l'affiliation est obligatoire.
- Déduction de coordination : soustraite du salaire pour obtenir le salaire assuré.
- Salaire annuel déterminant maximum : trois fois le seuil d'entrée.
- Bonification : pourcentage du salaire assuré, minimum légal par tranche d'âge.
Valeurs de référence 2026 (à revérifier chaque année, elles sont indexées sur la rente AVS maximale) :
| Paramètre | 2026 | 2027 (annoncé) |
|---|---|---|
| Seuil d'entrée LPP | 22'680 | 23'040 |
| Déduction de coordination | 26'460 | 26'880 |
| Salaire déterminant maximum (3 × seuil) | 68'040 | 69'120 |
| Salaire coordonné maximum | 41'580 | 42'240 |
| Bonification 25-34 ans | 7 % | 7 % |
| Bonification 35-44 ans | 10 % | 10 % |
| Bonification 45-54 ans | 15 % | 15 % |
| Bonification 55-65 ans | 18 % | 18 % |
| Traitement de la période 2026 | — | vérifier le droit transitoire |
Points de contrôle :
- Affiliation : le contrat peut-il ne rien dire alors que le seuil est dépassé ? Non, l'affiliation est obligatoire, et l'employeur répond du défaut.
- Substitution : une indemnité en espèces « en lieu et place » de la LPP ne remplace pas les prestations minimales ; elle est en plus soumise à cotisations.
- Répartition : la part patronale doit être au moins égale à la part salariale (LPP 66 al. 1). Un plan 40/60 en faveur du salarié est en principe inadmissible sur la partie obligatoire.
- Salaire assuré : le minimum légal n'est pas le plan. Un employeur peut assurer un salaire plus élevé, ou un plan 1e sans garantie de prestation. Le contrat doit le dire ; à défaut, demander le règlement et le certificat d'assurance.
- Taux de conversion : pour la partie obligatoire, le taux minimal légal s'applique ; au-delà, c'est le règlement qui décide. Un taux de conversion bas pèse plus lourd, à long terme, que 200 CHF de salaire mensuel.
- Rachat : un droit de rachat n'est un avantage que si l'employeur finance ou co-finance. Vérifier les conditions et le délai de blocage.
4. Assurances sociales et coût employeur
Taux 2026 (parts salarié et employeur, à revérifier chaque année) :
| Cotisation | Salarié | Employeur | Base |
|---|---|---|---|
| AVS + AI + APG | 5.3 % | 5.3 % | salaire AVS |
| AC | 1.1 % | 1.1 % | jusqu'à 148'200 |
| AC solidarité | 1 % | — | au-delà de 148'200 |
| Allocations familiales | — | taux cantonal (~2.2 % JU) | salaire AVS |
| LAA accidents professionnels | — | prime selon risque | salaire LAA |
| LAA accidents non professionnels | prime | — sauf clause plus favorable | salaire LAA |
| LPP | part salariale | part patronale ≥ 50 % | salaire assuré |
| Perte de gain maladie (IJM) | selon contrat | selon contrat | salaire assuré |
LAA 91 : les primes des accidents professionnels sont à la charge de l'employeur, celles des accidents non professionnels à la charge du travailleur. Une clause qui met l'ANP à la charge de l'employeur est plus favorable et doit être valorisée.
5. Fin du contrat
- CDD : prend fin à l'échéance, sans congé (CO 334). Un enchaînement de CDD sans justification objective peut être abusif ; un CDD de plus de 10 ans est réputé CDI.
- CDI : résiliation en tout temps, forme écrite (CO 335), délais CO 335a, essai CO 335b.
- Abus : CO 336 (motifs) et 336a (indemnité jusqu'à 6 mois de salaire).
- Périodes protégées : CO 336c, avec suspension du délai de congé.
- Licenciement collectif : CO 335d-335h dès les seuils d'effectif, plan social et consultation.
Un contrat qui prévoit un délai de congé plus court que la loi est nul sur ce point ; un contrat qui prévoit une indemnité de départ forfaitaire peut valoir mieux qu'un long préavis, à chiffrer.
6. Requalification — le piège des contrats « différents »
Un contrat intitulé mandat, freelance ou consultant, mais qui impose horaires, lieu, subordination, intégration à l'organisation et outils de l'employeur, est un contrat de travail. Conséquences : AVS, LPP, LAA, impôt à la source, protection contre le licenciement.
Indices à relever : exclusivité, rémunération fixe mensuelle, absence de risque économique, ordres reçus, rapports hiérarchiques, congés posés, matériel fourni.
C'est le premier contrôle à faire quand les contrats comparés ne sont pas de même nature juridique : comparer un salarié à un indépendant n'a aucun sens si l'indépendant est requalifiable.
7. Impôt et statut
- Impôt à la source pour les permis B sans C, les frontaliers et certains permis L ; déclaration ordinaire pour les Suisses et les permis C.
- Le net fiscal n'entre pas dans le comparatif : il dépend du canton, de la commune, de l'état civil, des enfants et des charges. Il se simule séparément, avec les barèmes de l'année.
- Les frais forfaitaires non imposables, les avantages en nature et les cotisations LPP modifient le revenu imposable : à signaler, pas à chiffrer dans le tableau.
8. Procédure de vérification (à faire à chaque comparatif)
- Fixer l'année de référence et le canton.
- Lire chaque norme citée dans sa version applicable (
get_law,as_ofà la date de signature ou de l'offre). - Pour chaque jurisprudence invoquée,
find_relevant_erwaegungpuiscite(): le considérant existe-t-il, et dit-il ce qu'on lui fait dire ? check_claim_support()sur les affirmations décisives (« cette clause est nulle », « la bonification minimale est de 15 % »).- Écrire dans le livrable la version des normes et l'année des taux.
Limites : le corpus open access ne contient pas les commentaires payants (CR, BSK, Berner Kommentar) ni toute la jurisprudence cantonale. Quand une réponse en dépend, le dire.
contrats-travail-ch/references/calculs.md
11 Ko · 253 lignes
Calculs et normalisation — formules, exemple, cas limites
Tout est en CHF par an, sauf mention contraire. Les taux sont ceux de l'année de référence
et se vérifient (voir cadre-juridique.md). Le script scripts/normaliser.py applique
exactement ces formules ; ce fichier explique ce qu'il fait, pour pouvoir le contredire.
1. Rémunération
fixe_annuel = salaire_mensuel × mensualites
bonus_espere = max(bonus_cible × probabilite, bonus_garanti)
brut_annuel = fixe_annuel + bonus_espere + autres_remunerations
frais_annuel = frais_forfait_mensuel × 12
cash_annuel = brut_annuel + frais_annuel
Pourquoi un max sur le bonus : une cible à 12'000 avec 80 % de probabilité vaut 9'600, un
garanti de 5'000 vaut 5'000. Prendre la somme reviendrait à compter deux fois la même
promesse. Si le bonus est purement discrétionnaire, le valoriser à 0 et le dire : en droit,
une gratification discrétionnaire n'est pas une créance.
2. Cotisations et coût employeur
cot_salarie = brut × 5.3 % (AVS/AI/APG)
+ min(brut, 148'200) × 1.1 % (AC)
+ prime ANP (LAA, sauf clause plus favorable)
+ part salariale LPP
cot_employeur = brut × 5.3 %
+ min(brut, 148'200) × 1.1 %
+ brut × taux AF cantonal
+ prime LAA accidents professionnels
+ part patronale LPP
+ prime IJM si payée par l'employeur
cout_employeur = brut + cot_employeur + frais
net_avant_impot = brut − cot_salarie
Le coût employeur sert à comparer deux propositions du point de vue de l'entreprise (utile en négociation : « votre offre coûte 125'110, la leur 120'823, il y a 4'287 de marge »). Le net avant impôt sert à comparer le cash réel, avant le fisc.
3. LPP
salaire_determinant = min(brut, 3 × seuil_entree)
salaire_assure = max(0, salaire_determinant − deduction_coordination)
(ou le montant explicitement prévu par le plan)
lpp_total = salaire_assure × taux_bonification
lpp_employeur = lpp_total × part_patronale
lpp_salarie = lpp_total − lpp_employeur
Trois pièges :
- Le brut inclut le 13e et le bonus : ils sont soumis à l'AVS, donc au salaire déterminant.
- Le salaire assuré peut être supérieur au minimum légal si le plan le prévoit (ou inférieur pour la partie surobligatoire d'un plan 1e). Le contrat ou le règlement fait foi.
- Un taux de bonification total de 18 % avec 50 % patronal ne vaut pas 18 % de plus que 15 % avec 60 % patronal : c'est la cotisation patronale en CHF qui se compare.
Trajectoire (facultatif, pour un horizon long)
capital_patronal_projete ≈ Σ (salaire_assure × taux_bonif(age) × part_patronale)
de l'âge actuel à 65 ans, sans intérêts
C'est un ordre de grandeur, pas une projection actuarielle : les bonifications augmentent par tranche d'âge et le salaire change. Il sert à montrer qu'un écart de 1'300 CHF/an à 38 ans pèse environ 35'000 CHF de cotisations sur la carrière, avant intérêts.
4. Temps
heures_annuelles = horaire_hebdo × 52
heures_effectives = heures_annuelles − vacances_semaines × horaire_hebdo
− jours_feries × horaire_hebdo / jours_par_semaine
taux_horaire = brut_annuel / heures_effectives
heures_effectives est le temps réellement travaillé : les vacances et les fériés sont
payés mais ne sont pas du travail. C'est la seule base honnête pour comparer 40 h et 42 h.
valeur_jour_libre = brut_annuel / (260 − jours_feries)
Cette valeur permet de chiffrer une 5e semaine de vacances : environ 5 × 440 = 2'200 CHF pour un brut de 110'500. Sans ce chiffre, la semaine supplémentaire ne pèse rien dans la décision alors qu'elle coûte 2'200 CHF à l'employeur.
5. Package et seuil de bascule
écart_heures = (heures_reference − heures_contrat) × taux_horaire_reference
package_complet = cash + prevoyance_patronale + ecart_heures
seuil(B/A) = (cash_A − cash_B)
+ (prevoyance_A − prevoyance_B)
+ (heures_B − heures_A) × taux_horaire_A
Le temps est valorisé au taux horaire de la référence : c'est le prix auquel on « rachète » les heures non travaillées. Les vacances, les fériés et l'horaire hebdomadaire sont déjà dans les heures effectives, donc on ne les compte pas une deuxième fois. La « valeur d'un jour libre » reste utile pour communiquer (« la 5e semaine vaut 2'200 CHF »), pas pour calculer un classement.
Un seuil positif est le montant annuel à ajouter à B pour égaler A. Un seuil négatif signifie que B est déjà au-dessus.
6. Pas de score composite
Un indice 0-100 construit par min-max écrase les écarts : 21'000 CHF de salaire en moins peuvent peser autant qu'un écart de 25 points sur une note de sécurité. Le résultat est un classement qui contredit l'arithmétique.
Remplacé par deux montants en francs, formules affichées :
| Package | Formule | Usage |
|---|---|---|
| Cash (12 mois) | brut + frais forfaitaires | décision à 1 an, trésorerie |
| Package complet | cash + prévoyance patronale + heures non travaillées valorisées | décision à 5-10 ans |
La sécurité et les conditions ne sont pas monétisées : elles restent qualitatives, et un
contrat qui porte une clause bloquante est hors classement (marqué h.c.), quel que
soit son package. Une clause nulle ne se compense pas avec 3'000 CHF.
7. Exemple chiffré (jeu de données templates/contrats-exemple.json)
Trois contrats, âge 38, canton JU, année 2026 :
- A : CDI, 8'500 × 13, 42 h, 4 semaines, LPP 15 % à 50/50, préavis 3 mois.
- B : CDI, 7'800 × 12 + bonus cible 12'000 (80 %), 40 h, 5 semaines, LPP 18 % à 60/40, frais 200/mois, 2 jours de télétravail, préavis 2 mois.
- C : CDD, 9'500 × 12, 45 h, 4 semaines, pas de LPP, pas d'IJM, non-concurrence non écrite.
| Indicateur | A | B | C |
|---|---|---|---|
| Brut annuel | 110'500 | 103'200 | 114'000 |
| Cash annuel | 110'500 | 105'600 | 114'000 |
| Salaire assuré LPP | 41'580 | 41'580 | 0 |
| Prévoyance patronale | 4'002 | 5'316 | 0 |
| Coût employeur | 125'110 | 120'823 | 124'944 |
| Heures effectives | 1'940 | 1'808 | 2'079 |
| Taux horaire | 56.95 | 57.08 | 54.83 |
| Jours libres | 29 | 34 | 29 |
| Package complet | 114'502 | 118'456 | 106'107 (h.c.) |
Lecture :
- B vaut 3'954 CHF/an de plus que A sur le package complet : 4'900 de cash en moins, compensés par 1'314 de prévoyance et 132 heures de moins, valorisées 7'540 au taux horaire de A. Autrement dit, chaque heure libérée coûte 37 CHF de cash en moins, alors qu'elle se valorise 57 CHF au taux de A : c'est un bon achat de temps, pas un mauvais salaire.
- B a pourtant le taux horaire le plus élevé (57.08 contre 56.95) : à 40 h, chaque heure vaut plus cher. L'écart de 4'900 sur le cash vient du 13e et de l'horaire, pas du niveau de rémunération.
- C affiche 3'500 de cash de plus que A, mais 4'002 de prévoyance en moins et 139 heures de plus : il lui manque 8'395 CHF/an, et il est hors classement (LPP obligatoire non respectée, non-concurrence nulle).
- Le choix se joue donc entre le cash immédiat (A) et le temps + la prévoyance (B), pour 3'954 CHF d'écart : c'est un arbitrage de préférence, pas de calcul. La skill doit le dire ainsi, pas désigner un gagnant.
8. Cas limites
Temps partiel. Comparer d'abord à 100 % équivalent (brut / taux), puis au taux réel. Un 60 % à 5'400 vaut 9'000 à 100 %, ce qui est souvent moins que le poste à 100 % proposé affiche. Les charges fixes (abonnements, LPP, frais) ne se réduisent pas proportionnellement.
13e payé par tranches. Aucun impact annuel, seulement un impact de trésorerie. Ne pas le compter deux fois.
Bonus avec clawback ou différé. Actualiser : un bonus versé à 50 % maintenant et 50 % à 12 mois vaut moins, et un clawback est une option au profit de l'employeur.
Frais forfaitaires élevés. Si le forfait dépasse manifestement les frais réels (repas, déplacements), l'AVS peut le requalifier en salaire. À signaler comme risque, et comparer les deux scénarios.
Salaire au-dessus du plafond AC (148'200). La part AC employeur plafonne, la part salarié aussi ; au-delà, seule la cotisation de solidarité (1 %, salarié) s'applique. Le taux marginal employeur baisse, le coût marginal aussi.
Salaire au-dessus du salaire déterminant LPP maximum. La bonification obligatoire plafonne ; le plan peut assurer davantage. Deux packages identiques au-delà de 68'040 peuvent donc être très différents en prévoyance.
Plan 1e. Pas de garantie de prestation au-delà de la partie obligatoire, pas de taux de conversion minimum sur la partie surobligatoire. Le comparatif doit le signaler comme un risque, pas comme une économie.
CDD. Pas de délai de congé, pas de protection 336c, pas de bonus prorata garanti. Et un CDD renouvelé plusieurs fois peut être requalifié : le comparatif doit le dire avant de comparer.
Annualisation. Si l'horaire est annualisé, les heures au-delà de la moyenne vont dans un compte. Elles ne sont pas perdues, mais elles ne sont pas du cash : les traiter comme de la valeur différée, et vérifier le plafond et les conditions de liquidation.
9. Reconstituer un salaire à 100 % depuis des pièces partielles
Un certificat de salaire couvre rarement une année pleine au même taux. Pour retrouver le
mensuel 100 % M à partir du brut documenté :
brut = M × Σ(taux_i × mois_i) × 13/12 # avec 13e salaire
brut = M × Σ(taux_i × mois_i) # sans 13e
=> M = brut / (Σ taux_i × mois_i × 13/12)
annuel_100 = 13 M
Exemple : 10 mois à 90 % + 2 mois à 40 % = 9.8 mois équivalent plein temps ; avec 13e, facteur 10.617.
Quatre contrôles, dans cet ordre :
- Recouper avec une autre année. L'écart implicite doit être de 0 à 3 %/an dans le public, jusqu'à 5 à 8 % dans le privé. Au-delà, le calendrier des taux ou le traitement du 13e est faux.
- Recouper avec un décompte de salaire : il affiche souvent « salaire mensuel à 100 % ».
- Si
M × 13du contrat est inférieur au brut du certificat, il y a un bonus ou une augmentation en cours d'année. Ne pas forcer le modèle : l'écrire. - Si le certificat de travail et le contrat se contredisent sur le taux d'activité, le décompte de salaire tranche. Un certificat réécrit l'histoire, un décompte ne la réécrit pas.
Le résultat se donne en fourchette, pas en chiffre unique : deux pièces qui concordent à ±2 % valent mieux qu'une reconstitution présentée comme exacte.
Valoriser un poste passé à son salaire de sortie, pas à celui d'entrée : c'est le niveau que le poste avait atteint, et le seul comparable à une offre actuelle. Le salaire d'entrée sert à montrer la progression, pas à comparer.
10. Erreurs de calcul à ne pas commettre
- Oublier le 13e dans le brut, puis s'étonner d'un écart de 8 %.
- Prendre la bonification totale pour la part patronale.
- Calculer le taux horaire sur 52 × 42 h sans déduire vacances et fériés.
- Comparer des jours de vacances en jours ouvrés et en jours calendaires.
- Additionner le bonus cible et le bonus garanti.
- Mettre les frais forfaitaires dans le brut, puis les compter une deuxième fois dans le cash.
- Oublier la prime IJM payée par l'employeur dans la prévoyance.
- Comparer des contrats à taux d'activité différents sans équivalent 100 %.
- Utiliser les taux de l'année précédente sur des offres signées en janvier.
- Donner un net après impôt sans simulateur cantonal : c'est une invention, pas un calcul.
contrats-travail-ch/references/negociation-remuneration.md
6 Ko · 141 lignes
Négocier la rémunération à partir de l'économie de l'employeur
Pour une demande d'augmentation, une offre « au rabais » ou un « on ne peut pas se le permettre ». Le principe : ne pas argumenter avec le marché seul, mais avec la valeur produite et la structure de coûts de l'employeur. Un salaire se compare à ce qu'il rapporte, pas à ce qu'il coûte.
1. Fixer et afficher les entrées
Taux journalier facturé, objectif de jours facturés, effectif, qui facture et à quel taux. Les écrire en tête de la note. Un modèle bâti sur un chiffre non confirmé s'effondre à la première correction : si l'objectif passe de 270 à 170 jours, l'argument « heures supplémentaires déguisées » disparaît et la marge de la boîte double. Les entrées se valident avant de calculer, pas après.
2. Tester la cohérence de l'objectif de facturation
jours ouvrables = 260 − jours fériés cantonaux
jours disponibles = jours ouvrables − jours de vacances
utilisation = objectif de jours facturés / jours disponibles
- > 100 % : l'objectif n'est atteignable qu'en heures supplémentaires. Le surplus est du CA produit sans salaire ; s'il était payé, ce serait +25 % (CO 321c). C'est l'argument le plus fort du dossier, et il est arithmétique.
- 70-80 % : objectif normal. Ne pas l'attaquer : le levier est ailleurs (jours non facturés, taux, structure).
- < 60 % : problème commercial, pas problème de salarié. Ne pas accepter un variable de facturation indexé sur un objectif inatteignable.
3. Modéliser le chiffre d'affaires
CA = jours facturés × taux journalier
Trois scénarios (bas / central / haut), en distinguant les facturants des postes de direction qui facturent peu. Regarder ensuite le CA par tête — c'est le seul ratio comparable d'une structure à l'autre, et il se calcule : pas besoin de benchmark externe.
4. Modéliser les coûts
salaire chargé = salaire brut × 1.15 à 1.18 (AVS/AC/AF/LAA/LPP patronale)
frais généraux = 15 à 20 % du CA (locaux, IT, admin, marketing, formation)
point mort = (masse salariale chargée + frais) / taux journalier
Le point mort rapporté à la capacité de facturation dit la vraie contrainte : à 90 % du scénario central, il n'y a pas de coussin pour une erreur de recrutement ; à 50 %, l'objectif de facturation est une question de marge, pas de survie.
5. Caler la marge sur le secteur, pas sur l'intuition
Ordre de grandeur suisse (SSIS, Université de Berne pour Swico, éditions 2025-2026) :
| Indicateur | Valeur |
|---|---|
| Marge EBITDA, branche logicielle CH, 2024 | 6.4 % (9.5 % en 2023) |
| Marge EBITDA, 2026 | 7.6 % |
| Marge EBITDA, sociétés de conseil, 2026 | 11.9 % |
Une PME de services IT tourne donc à 6-12 %, pas à 30 %. Supposer une marge de 40 % produit un discours que l'employeur démonte en une phrase, et qui détruit la crédibilité du reste.
6. Les ratios qui portent l'argument
| Ratio | Formule | Ce qu'il dit |
|---|---|---|
| Coût chargé / jour facturé | salaire × 1.17 / jours facturés | ce que coûte une journée de la personne |
| Marge / jour | taux − coût / jour | ce que la boîte gagne sur chaque jour |
| CA généré / coût total | CA / (salaire chargé) | 2.5 à 3.5× est la norme du conseil |
| Coût d'une augmentation | (Δsalaire × 1.17) / taux | en jours facturés : 10k ≈ 7 jours à 1'600 |
| Coût d'un point de LPP | salaire coordonné × 0.5 × Δtaux | souvent < 1 jour facturé |
Le ratio CA/coût après augmentation est l'argument le plus simple à tenir en réunion : « on passe de 2.9× à 2.4×, toujours dans la norme ».
7. Construire un package, pas une demande unique
| Axe | Ce qu'on demande | Coût employeur |
|---|---|---|
| Intitulé | suppression de « junior », niveau réel | 0 |
| Fixe | +10 à +20 % | 7 à 15 jours facturés |
| Variable | % de la marge au-delà de l'objectif de jours | auto-financé |
| LPP | +5 points de bonification | souvent < 1 jour facturé |
| Contrat | CDI, préavis légal, clauses abusives supprimées | 0 |
| Formation | budget + jours | 3 à 7 jours facturés |
Le variable est la porte de sortie : il permet un oui sans toucher au fixe, et il aligne l'autonomie demandée sur le résultat. Le calculer sur la marge au-delà du seuil, pas sur le CA : la marge par jour est connue (taux − coût chargé), et en céder 20 % ne coûte rien.
8. Ordre de la discussion
- La valeur produite, en trois chiffres : jours × taux = CA, pour un coût chargé de X.
- La contrainte de l'employeur, reconnue à voix haute (marge sectorielle, point mort).
- L'intitulé (gratuit, et il conditionne la grille et le poste suivant).
- Le fixe.
- Le variable comme mécanisme, pas comme concession.
- La LPP et les clauses, en dernier : ce sont les gestes les moins chers.
9. Les cinq chiffres à demander à l'employeur
- Taux d'utilisation facturé moyen de l'exercice (jours facturés / jours disponibles).
- CA et taux journalier moyen réel après remises et forfaits.
- Masse salariale chargée / CA.
- Frais généraux / CA, et leur composition.
- Destination de la marge : réserve, investissement, rémunération de la direction.
Avec ces cinq réponses, le modèle devient exact et la demande se chiffre toute seule.
Pièges
- Un benchmark inventé contamine toute la note. Chaque chiffre externe porte sa source et son année ; sinon il s'annonce comme une estimation. Un ratio dérivé des données de l'employeur vaut mieux qu'une moyenne de marché non sourcée : il est vérifiable sur place.
- Monétiser les vacances comme un avantage. À 1'600/jour, une semaine de congé coûte 8'000 de CA potentiel. C'est un arbitrage, pas un dû : ne pas le demander en même temps que le fixe.
- Comparer l'augmentation au CA au lieu du bénéfice. +15k sur 100k d'EBIT, c'est 15 % du résultat. Dire « c'est petit pour vous » quand c'est 15 % du bénéfice fait perdre la négociation ; dire « je le finance par 11 jours facturés » la gagne.
- Confondre CA par tête et marge par tête. Le premier ne dit rien de la rentabilité.
- Garder une période dégradée dans le raisonnement de valeur (formation, ramp-up) : elle baisse artificiellement le CA généré et sert l'employeur. La sortir et la chiffrer à part.
- Prendre le taux affiché pour le taux moyen. Les remises et forfaits coûtent 10-20 %.
- Traiter un poste de direction qui facture peu comme un parasite. C'est un coût de structure : il s'agit de savoir s'il est refacturé dans le taux, pas de le supprimer.
Spécificités suisses utiles
- Heures supplémentaires : +25 % ou compensation en temps (CO 321c), renonciation écrite seulement, et encadrée.
- LPP : minima 7/10/15/18 % selon l'âge sur le salaire coordonné ; passer de 10 à 15 % coûte ~1'300 CHF/an à l'employeur pour un salaire de 80'000, soit moins d'un jour facturé.
- Le 13e salaire change la lecture du mensuel : 80'000 en 12 fois = 6'667/mois, l'équivalent en 13 fois = 6'154 × 13. Toujours afficher le nombre de mensualités.
contrats-travail-ch/references/referentiel-champs.md
10 Ko · 146 lignes
Référentiel de comparaison — les champs à extraire
Une soixantaine de champs, regroupés en 12 blocs. Pour chacun : ce qu'on lit dans le contrat, et la conversion ou le piège qui va avec. Un champ non renseigné dans le contrat n'est pas zéro : c'est le droit supplétif ou impératif qui s'applique, ou un point à faire confirmer par écrit.
Convention de notation dans l'annexe de données :
- valeur chiffrée ou texte : ce que dit le contrat ;
null: silence du contrat ;"à vérifier": le document ne permet pas de conclure.
A. Cadre du contrat (6)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| A1 Type | CDI, CDD, apprentissage, stage, mandat | CDD : pas de délai de congé, pas de protection 336c ; enchaînement = risque de requalification en CDI. Mandat avec subordination = salarié déguisé. |
| A2 Canton et CCT | siège de l'employeur, champ d'application d'une CCT/CCNT | Détermine fériés, allocations familiales, impôt à la source, salaires minimaux. Une CCT étendue prime sur le contrat. |
| A3 Taux d'activité | 100 %, 80 %, 60 % | Comparer à 100 % équivalent puis au taux réel. Un 80 % peut battre un 100 % en taux horaire. |
| A4 Fonction et niveau | intitulé, classe, niveau hiérarchique | Aucune valeur juridique, mais conditionne le marché, le statut cadre (heures sup) et le plan LPP. |
| A5 Lieu, mobilité, télétravail | lieu principal, clause de transfert, jours à distance | Temps de trajet non payé en principe ; télétravail = valeur des trajets évités + clause de rappel à vérifier. |
| A6 Début, ancienneté, séjour | date d'entrée, reprise d'ancienneté, permis | L'ancienneté commande le délai de congé (335a), les vacances par année de service et le vesting LPP. |
B. Rémunération fixe (6)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| B1 Salaire mensuel brut | montant, base 100 % | Ne jamais comparer un mensuel à un mensuel : annualiser. |
| B2 Mensualités | 12, 13, 14 | Facteur direct du brut annuel. Un « 8'500 » à 13 fois vaut 110'500, pas 102'000. |
| B3 13e salaire | contractualisé, usage, ex gratia, prorata | S'il est ex gratia, le valoriser à la moyenne des 3 dernières années ou à 0, et le dire. |
| B4 Allocations | fonction, région, cherté, pénibilité | Soumises AVS : à intégrer au brut, pas aux frais. |
| B5 Indexation | clause d'adaptation au renchérissement | Aucune obligation légale. Valeur 0 par défaut, scénario séparé. |
| B6 Augmentations | grille, échéances, entretien annuel | Probabiliste : ne jamais l'intégrer au brut de référence, seulement en scénario. |
C. Rémunération variable (4)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| C1 Bonus | cible, maximum, garantie, prorata temporis | Espérance = cible × probabilité, plancher = garanti. Présenter les trois (garanti / cible / max). |
| C2 Conditions de versement | présence, performance individuelle et collective, résultat | Une clause discrétionnaire vaut 0 en droit ; une clause chiffrée vaut une créance. |
| C3 Commission | taux, base de calcul, territoire, clients | Vérifier si la commission est due sur les affaires conclues après le départ (clause de sortie). |
| C4 Participation, actions, options | nombre, prix d'exercice, vesting, lock-up | Valoriser au coût employeur ou à la valeur de marché × fraction acquise. Un plan illiquide ne vaut pas son affichage. |
D. Frais et avantages en nature (5)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| D1 Forfait frais | montant mensuel, base | Non soumis s'il correspond à des frais réels et est reconnu par l'AVS. Sinon : salaire déguisé, cotisations rétroactives. |
| D2 Remboursement au réel | notes de frais, plafonds, délais | Neutre pour le comparatif, mais vérifier les plafonds et le délai de remboursement. |
| D3 Voiture, transport | véhicule de fonction, abonnement CFF, parking | Valeur = coût réel ; part privée imposable selon barème cantonal. Un abonnement CFF n'a pas la valeur d'une voiture. |
| D4 Outils | laptop, téléphone, internet | Valeur d'usage, rarement imposable. Vérifier si l'usage privé est autorisé. |
| D5 Formation | budget, jours, clause de remboursement | Valeur = coût ; mais la clause de remboursement en cas de départ est une dette conditionnelle à chiffrer. |
E. Temps de travail (5)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| E1 Horaire hebdomadaire | 40, 42, 45, annualisé | Base du taux horaire. 40 h vs 42 h = 5 % de temps en plus pour le même salaire. |
| E2 Annualisation, CET | période de référence, solde, plafond | Déplace les heures au-delà de la moyenne dans un compte : valeur différée, pas cash. |
| E3 Heures supplémentaires | payées, compensées, exclues | CO 321c : +25 % ou compensation en temps, sauf renonciation écrite (souvent réservée aux cadres). Une exclusion orale ne vaut rien. |
| E4 Astreintes, piquets, rappels | indemnité, délai d'intervention | Le temps d'astreinte n'est pas du travail effectif, mais sa sujétion se paie. |
| E5 Pauses, trajet, nuit, dimanche | durée, compensation | Pauses courtes = temps de travail ; trajet domicile-travail = hors travail ; suppléments LTr pour nuit et dimanche. |
F. Absences et vacances (7)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| F1 Vacances | semaines ou jours, base 5 ou 6 jours | Minimum 4 semaines, 5 jusqu'à 20 ans révolus (CO 329a). Convertir en heures, jamais en « jours » ambigus. |
| F2 Jours fériés | liste, nombre, ponts | Cantonal. Compter, ne pas supposer : 9 à 12 selon le canton et l'usage. |
| F3 Maladie | 100 % ou 80 %, durée, carence, qui paie la prime | CO 324a : 3 semaines puis 3 mois à 100 %, ou assurance équivalente 80 % / 720 jours. Comparer sur 12 mois d'incapacité. |
| F4 Accident | LAA 80 %, complément 90/100 %, carence | Vérifier qui paie la prime du complément et la durée du supplément. |
| F5 Maternité, paternité, parental | 14 semaines, 2 semaines, au-delà | APG 80 % plafonné ; tout le reste est contractuel et se compare en jours. |
| F6 Congés spéciaux | mariage, décès, déménagement, jeunesse, proche aidant | Jours payés, souvent oubliés dans les comparatifs. Les additionner. |
| F7 Réduction pendant l'incapacité | vacances, 13e, bonus | Un contrat qui réduit les vacances en cas de maladie longue coûte plus cher qu'il n'y paraît. |
G. Prévoyance (6)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| G1 Caisse et plan | institution, primauté cotisations / prestations / 1e | Plan 1e = pas de minimum légal au-delà de la part obligatoire, risque pour l'assuré. |
| G2 Salaire assuré | montant, déduction de coordination | Défaut légal : min(brut, 3 × seuil) − déduction. Un plan peut assurer plus : le contrat doit le dire. |
| G3 Bonification et part patronale | taux total, répartition | Minimums 7/10/15/18 % selon l'âge (LPP 16) ; part patronale ≥ part salariale (LPP 66). |
| G4 Prestations | taux de conversion, rente ou capital, rente conjoint | Le taux de conversion et la rente de conjoint valent plus que la cotisation annuelle. |
| G5 Rachat, retraite anticipée | possibilité, conditions, financement | Un droit de rachat n'est pas un avantage tant qu'il n'est pas financé par l'employeur. |
| G6 LAA, IJM, 3e pilier | primes, complément, participation employeur | LAA : AP à charge employeur, ANP à charge salarié (LAA 91), sauf clause plus favorable. |
H. Fin du contrat (5)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| H1 Durée | indéterminée, déterminée, terme | Un CDD à terme imprécis peut être requalifié. |
| H2 Essai | durée, prolongation, préavis | 1 mois, 3 mois maximum par écrit, 7 jours de préavis (CO 335b). |
| H3 Délai de congé | mois, paliers d'ancienneté | 1 mois la 1re année, 2 mois de la 2e à la 9e, 3 mois dès la 10e (CO 335a) ; jamais moins d'un mois. |
| H4 Protection | maladie, accident, grossesse, service | CO 336c : 30 / 90 / 180 jours selon l'ancienneté. Un contrat qui l'ignore ne la supprime pas. |
| H5 Certificat, références | droit, contenu, clause de non-dénigrement | Droit au certificat (CO 330a) ; vérifier ce que l'employeur s'engage à écrire. |
I. Clauses restrictives (5)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| I1 Non-concurrence | durée, territoire, activité, pénalité, contrepartie | Écrite, 3 ans maximum, intérêt légitime, contrepartie adéquate (CO 340-340c). Sans écrit : nulle. |
| I2 Non-sollicitation | clients, collaborateurs, durée | Souvent requalifiée en non-concurrence si elle vide la capacité de travailler. |
| I3 Exclusivité | activité accessoire autorisée ou non | CO 321a al. 3 : l'employeur peut l'interdire, mais l'interdiction doit être écrite et justifiée. |
| I4 Confidentialité | périmètre, durée post-contractuelle | Doit être limitée dans le temps et l'objet, sinon attentatoire à la liberté économique. |
| I5 Propriété intellectuelle | inventions, œuvres, logiciels | CO 332 pour les inventions et designs ; droit d'auteur pour les œuvres. Vérifier la cession des droits futurs. |
J. Discipline et contrôle (3)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| J1 Règlement intérieur | existence, opposabilité | Doit avoir été remis et accepté ; un règlement non communiqué n'est pas opposable. |
| J2 Sanctions, amendes | avertissement, amende, mise à pied | Les amendes pécuniaires sont illégales. La mise à pied n'existe pas comme sanction autonome en droit du travail. |
| J3 Contrôles | géolocalisation, emails, caméras, badge | Proportionnalité et information préalable (LPD, CO 328). Un contrôle disproportionné est illicite. |
K. Carrière (3)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| K1 Évaluation | fréquence, conséquences | Un entretien sans conséquence salariale n'a pas de valeur économique. |
| K2 Formation | jours, budget, temps payé | Valeur = coût + temps payé ; vérifier la clause de remboursement. |
| K3 Promotion, mobilité | grille, mobilité interne, expatriation | À traiter en scénario : valeur = probabilité × gain. |
L. Conformité (4)
| Champ | Ce qu'on lit | Normalisation / piège |
|---|---|---|
| L1 Permis de travail | type, durée, activité autorisée | Un permis B/C ne vaut pas un permis L ; le changement d'employeur peut être soumis à autorisation. |
| L2 Cotisations et affiliations | AVS, LPP, LAA, IJM | Vérifier les attestations, pas les promesses. Une affiliation annoncée et non faite engage l'employeur. |
| L3 Égalité salariale | grille, positionnement | LEl : contrôle possible ; un écart inexpliqué est un risque, pas un argument de négociation. |
| L4 Intégrité | harcèlement, lanceur d'alerte, RC | Vérifier l'existence d'une procédure interne et d'une couverture RC. |
Ce qui ne se met pas dans le référentiel
- Le « salaire cible » d'une promesse orale non écrite.
- Les avantages dont le coût n'est pas identifiable (culture d'entreprise, prestige).
- Les éléments dépendant d'une décision future et discrétionnaire (promotion annoncée).
- Le net fiscal : il dépend du canton, de l'état civil, des enfants, des charges. Il se calcule à part, avec un simulateur, jamais dans le tableau de comparaison.
Ces éléments peuvent apparaître en note qualitative, jamais en ligne chiffrée.
contrats-travail-ch/templates/comparatif-contrats.md
5 Ko · 206 lignes
Comparatif de contrats de travail —
Contrats comparés : A (employeur, intitulé), B, C Référence : A Date : JJ.MM.AAAA Année fiscale et sociale de référence : 20XX Canton : XX — Âge : XX — Taux visé : XX % CCT applicable : oui / non / à vérifier
1. Synthèse
Cinq lignes maximum. Le verdict, l'écart en CHF, ce qui fait basculer.
Exemple : « B est la meilleure offre à long terme : 4'900 CHF de cash en moins que A, compensés par 1'314 de prévoyance et 5 jours de congé, soit un seuil de bascule de 1'385 CHF/an. A reste devant si l'horizon est de 12 mois. C est hors classement : LPP obligatoire non respectée. »
| A | B | C | |
|---|---|---|---|
| Cash annuel | |||
| Prévoyance patronale | |||
| Jours libres | |||
| Taux horaire effectif | |||
| Package complet | |||
| Seuil de bascule | réf. | ||
| Points bloquants |
2. Ce qui n'est pas comparable directement
Trois à cinq points, avant tout chiffre. Sinon le lecteur compare des pommes et des poires.
- A est à 42 h, B à 40 h, C à 45 h : les taux horaires, pas les mensuels.
- A paie un 13e, B un bonus variable, C rien : le fixe de B est plus bas de 16'900.
- B inclut 2'400 de frais forfaitaires, non soumis à cotisations.
- C est un CDD : pas de préavis, pas de protection, clause de non-concurrence nulle.
3. Rémunération
Ce que paie chaque contrat, ramené à l'année.
| A | B | C | |
|---|---|---|---|
| Salaire mensuel | |||
| Mensualités | |||
| Fixe annualisé | |||
| Bonus (cible / max / garanti) | |||
| Autres rémunérations | |||
| Brut annuel | |||
| Frais forfaitaires | |||
| Cash annuel | |||
| Net avant impôt |
Écarts vs référence, en CHF et en % :
- …
Ce qu'il faut demander : le bonus est-il garanti la première année ? Le 13e est-il contractualisé ou d'usage ? Les frais couvrent-ils des dépenses réelles ?
4. Prévoyance
Le poste le plus souvent mal comparé, et le plus lourd à long terme.
| A | B | C | |
|---|---|---|---|
| Caisse et plan | |||
| Salaire assuré | |||
| Bonification totale | |||
| Part patronale | |||
| Cotisation patronale / an | |||
| Cotisation salariale / an | |||
| Taux de conversion | |||
| LAA (AP / ANP, complément) | |||
| IJM : % / durée / carence | |||
| 3e pilier employeur |
Écart de cotisation patronale sur la carrière (ordre de grandeur, sans intérêts) : …
Ce qu'il faut demander : le règlement de la caisse et le certificat d'assurance ; le salaire assuré est-il le minimum légal ou un plan plus large ; qui paie la prime du complément LAA et celle de l'IJM ; les bonifications sont-elles stables ou liées à l'âge.
5. Temps et absences
| A | B | C | |
|---|---|---|---|
| Horaire hebdomadaire | |||
| Heures effectives / an | |||
| Vacances (semaines / jours) | |||
| Jours fériés | |||
| Jours libres totaux | |||
| Heures supplémentaires | |||
| Astreintes | |||
| Télétravail |
Valeur d'un jour libre : … CHF. Valeur d'une semaine de vacances supplémentaire : … CHF.
Ce qu'il faut demander : les heures sup sont-elles payées, compensées ou exclues par écrit ; les fériés sont-ils ceux du canton ou une liste limitative ; le télétravail est-il un droit ou une tolérance révocable.
6. Sécurité et fin du contrat
| A | B | C | |
|---|---|---|---|
| Type et durée | |||
| Période d'essai | |||
| Délai de congé | |||
| Protection 336c | |||
| Maladie : % / durée / carence | |||
| Accident : % / complément | |||
| Maternité / paternité | |||
| Certificat, références |
Ce qu'il faut demander : le délai de congé s'applique-t-il pendant l'essai ; la couverture maladie est-elle une assurance ou la seule obligation CO 324a ; que se passe-t-il en cas de maladie de plus de trois mois.
7. Clauses à risque
| Clause | A | B | C | Risque |
|---|---|---|---|---|
| Non-concurrence | ||||
| Non-sollicitation | ||||
| Exclusivité | ||||
| Confidentialité | ||||
| Propriété intellectuelle | ||||
| Mobilité / mutation | ||||
| Sanctions / contrôles |
Ce qu'il faut demander : la contrepartie de la non-concurrence ; la portée exacte du territoire et de l'activité interdite ; la cession des droits sur les créations futures.
8. Bascule
| A | B | C | |
|---|---|---|---|
| Package cash (12 mois) | |||
| Package complet | |||
| Écart de prévoyance sur la carrière (ordre de grandeur) |
Ce qui fait basculer : … (une phrase, chiffrée).
Seuil de bascule : B devrait payer … CHF/an de plus pour égaler A sur le package complet ; C devrait payer … CHF/an.
Ce qui n'est pas monétisé : la sécurité de l'emploi, la qualité du management, la trajectoire de carrière. À traiter en note qualitative, jamais en ligne chiffrée.
9. Points juridiques
| Contrat | Point | Sévérité | Base légale | Action |
|---|---|---|---|---|
| C | LPP non affiliée alors que le seuil est dépassé | bloquant | LPP 2, 7 | exiger l'affiliation avant signature |
| C | Non-concurrence non écrite | bloquant | CO 340 al. 1 | clause nulle, à supprimer |
| B | Couverture maladie 720 jours | info | CO 324a al. 4 | vérifier la police |
Sévérités : bloquant (clause nulle, à corriger avant signature) ; risque (validité contestable ou requalification possible) ; info (point à clarifier).
10. Questions à poser avant de signer
- …
- …
- …
Maximum 10 questions, classées par impact financier décroissant. Une question sans impact chiffrable ne mérite pas d'être dans la liste.
Annexe A — Données extraites
Le JSON utilisé par le script, ou le tableau des champs avec les à vérifier.
Annexe B — Hypothèses de calcul
Année, canton, taux appliqués, probabilité du bonus, valeur du jour libre, traitement des frais, part patronale retenue quand le contrat ne la donne pas.
Annexe C — Sources
Articles vérifiés (avec version), jurisprudence citée (avec considérant), règlements et polices fournis par l'employeur. Mentionner explicitement ce qui n'a pas été obtenu.
contrats-travail-ch/templates/contrats-exemple.json
2 Ko · 83 lignes
{
"parametres": {
"annee": 2026,
"af_emp": 2.2,
"laa_ap_emp": 1.0,
"laa_anp_sal": 1.2
},
"contrats": [
{
"id": "A",
"libelle": "PME industrielle, CDI, 13e, 42 h",
"type": "CDI",
"canton": "JU",
"age": 38,
"taux_activite": 1.0,
"salaire_mensuel": 8500,
"mensualites": 13,
"bonus": { "cible": 0, "probabilite": 1.0, "garanti": 0 },
"autres_remunerations": 0,
"frais_forfait_mensuel": 0,
"vacances_semaines": 4,
"jours_feries": 9,
"horaire_hebdo": 42,
"jours_par_semaine": 5,
"home_office_jours_semaine": 0,
"lpp": { "assure": true, "taux_bonif": 15, "part_employeur": 50, "plan": "primaute des cotisations" },
"laa": { "ap_taux": 1.0, "anp_taux": 1.2, "anp_a_charge": "salarie" },
"ijm": { "pct": 80, "duree_jours": 720, "carence_jours": 30, "prime_employeur": true, "prime_pct": 0.8 },
"delai_conge_mois": 3,
"periode_essai_mois": 3,
"non_concurrence": { "presente": false }
},
{
"id": "B",
"libelle": "Scale-up, CDI, bonus 12k cible, 40 h, 5 semaines",
"type": "CDI",
"canton": "JU",
"age": 38,
"taux_activite": 1.0,
"salaire_mensuel": 7800,
"mensualites": 12,
"bonus": { "cible": 12000, "probabilite": 0.8, "garanti": 0 },
"autres_remunerations": 0,
"frais_forfait_mensuel": 200,
"vacances_semaines": 5,
"jours_feries": 9,
"horaire_hebdo": 40,
"jours_par_semaine": 5,
"home_office_jours_semaine": 2,
"lpp": { "assure": true, "taux_bonif": 18, "part_employeur": 60, "plan": "primaute des cotisations" },
"laa": { "ap_taux": 1.0, "anp_taux": 1.2, "anp_a_charge": "salarie" },
"ijm": { "pct": 80, "duree_jours": 720, "carence_jours": 30, "prime_employeur": true, "prime_pct": 0.8 },
"delai_conge_mois": 2,
"periode_essai_mois": 3,
"non_concurrence": { "presente": false }
},
{
"id": "C",
"libelle": "CDD 12 mois, 45 h, pas de LPP, clause de non-concurrence",
"type": "CDD",
"canton": "JU",
"age": 38,
"taux_activite": 1.0,
"salaire_mensuel": 9500,
"mensualites": 12,
"bonus": { "cible": 0, "probabilite": 1.0, "garanti": 0 },
"autres_remunerations": 0,
"frais_forfait_mensuel": 0,
"vacances_semaines": 4,
"jours_feries": 9,
"horaire_hebdo": 45,
"jours_par_semaine": 5,
"home_office_jours_semaine": 0,
"lpp": { "assure": false },
"laa": { "ap_taux": 1.0, "anp_taux": 1.2, "anp_a_charge": "salarie" },
"ijm": { "pct": 0, "duree_jours": 0, "carence_jours": 0, "prime_employeur": false },
"delai_conge_mois": 0,
"periode_essai_mois": 3,
"non_concurrence": { "presente": true, "ecrite": false, "duree_mois": 24, "contrepartie": false }
}
]
}
contrats-travail-ch/scripts/normaliser.py
19 Ko · 480 lignes
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Normalise et compare des contrats de travail suisses sur un referentiel CHF / jours.
Usage:
python3 normaliser.py contrats.json
python3 normaliser.py contrats.json --md comparatif.md --json calculs.json
python3 normaliser.py contrats.json --reference A --annee 2026
Entree: {"parametres": {...}, "contrats": [...]} (voir templates/contrats-exemple.json)
Sortie: tableau markdown sur stdout, plus fichiers si demandes.
Stdlib uniquement. Tous les taux sont des parametres: rien n'est cache.
"""
import argparse
import json
import sys
# --------------------------------------------------------------------------- #
# Parametres par defaut, annee de reference 2026. A VERIFIER chaque annee.
# --------------------------------------------------------------------------- #
DEFAULTS = {
"annee": 2026,
"avs_sal": 5.3, # AVS 4.35 + AI 0.70 + APG 0.25
"avs_emp": 5.3,
"ac_sal": 1.1, # jusqu'au plafond
"ac_emp": 1.1,
"ac_plafond": 148200.0,
"af_emp": 2.2, # taux cantonal: VERIFIER (JU ~2.2)
"laa_ap_emp": 1.0, # prime accidents professionnels, a charge employeur
"laa_anp_sal": 1.2, # prime accidents non professionnels, a charge salarie
"lpp_seuil": 22680.0, # seuil d'entree LPP 2026
"lpp_deduction": 26460.0,
"lpp_max_det": 68040.0, # 3 x seuil d'entree
"bonif_min": {"25": 7.0, "35": 10.0, "45": 15.0, "55": 18.0},
"jours_ouvrables": 260, # 52 x 5, avant deduction des feries
"ijm_taux_defaut": 0.8, # % du brut si montant non fourni
}
def num(x, default=0.0):
if x is None or x == "" or x == "a verifier":
return default
if isinstance(x, (int, float)):
return float(x)
try:
return float(str(x).replace("'", "").replace(" ", "").replace(",", "."))
except ValueError:
return default
def bonif_minimum(age, params):
if age is None or age < 25:
return 0.0
if age >= 55:
return params["bonif_min"]["55"]
if age >= 45:
return params["bonif_min"]["45"]
if age >= 35:
return params["bonif_min"]["35"]
return params["bonif_min"]["25"]
MONETARY = (
"fixe_annuel", "bonus_espere", "brut_annuel", "frais_annuel", "cash_annuel",
"salaire_assure", "lpp_total", "lpp_emp", "lpp_sal", "cot_sal", "cot_emp",
"net_avant_impot", "cout_employeur", "prevoyance_annuelle", "taux_horaire",
"valeur_jour",
)
def compute(c, params):
"""Calcule toutes les grandeurs normalisees d'un contrat."""
params = dict(params)
params.update(c.get("parametres") or {}) # seuils de l'annee du contrat, taux cantonaux
taux = num(c.get("taux_activite"), 1.0) or 1.0
mensuel = num(c.get("salaire_mensuel"))
mensualites = num(c.get("mensualites"), 12.0)
fixe = num(c.get("salaire_annuel_fixe")) or mensuel * mensualites
bonus = c.get("bonus") or {}
bonus_cible = num(bonus.get("cible")) * num(bonus.get("probabilite"), 1.0)
bonus_garanti = num(bonus.get("garanti"))
bonus_espere = max(bonus_cible, bonus_garanti)
autres = num(c.get("autres_remunerations"))
brut = fixe + bonus_espere + autres
frais = num(c.get("frais_forfait_mensuel")) * 12.0
cash = brut + frais
# --- LPP -------------------------------------------------------------
lpp = c.get("lpp") or {}
age = c.get("age")
assure = lpp.get("assure", True)
sal_assure = num(lpp.get("salaire_assure"), None)
if sal_assure is None:
det = min(brut, params["lpp_max_det"])
sal_assure = max(0.0, det - params["lpp_deduction"])
taux_bonif = lpp.get("taux_bonif")
taux_bonif = num(taux_bonif) if taux_bonif is not None else bonif_minimum(age, params)
part_emp = num(lpp.get("part_employeur"), 50.0) / 100.0
cotisation_totale = lpp.get("cotisation_totale")
if not assure:
# Pas d'affiliation: ni salaire assure, ni bonification, ni part patronale.
sal_assure, taux_bonif, part_emp = 0.0, 0.0, 0.0
if cotisation_totale is not None:
# Montant documente (certificat de salaire, decompte) : il prime sur le calcul.
lpp_total = num(cotisation_totale)
if lpp_total and sal_assure > 0:
taux_bonif = lpp_total / sal_assure * 100.0
else:
lpp_total = sal_assure * taux_bonif / 100.0
lpp_emp = lpp_total * part_emp
lpp_sal = lpp_total * (1.0 - part_emp)
# --- LAA / IJM -------------------------------------------------------
laa = c.get("laa") or {}
ap_taux = num(laa.get("ap_taux"), params["laa_ap_emp"])
anp_taux = num(laa.get("anp_taux"), params["laa_anp_sal"])
anp_employeur = laa.get("anp_a_charge") == "employeur"
laa_emp = brut * ap_taux / 100.0 + (brut * anp_taux / 100.0 if anp_employeur else 0.0)
anp_sal = 0.0 if anp_employeur else brut * anp_taux / 100.0
ijm = c.get("ijm") or {}
prime_ijm = 0.0
if ijm.get("prime_employeur", False):
prime_ijm = num(ijm.get("prime_montant")) or brut * num(
ijm.get("prime_pct"), params["ijm_taux_defaut"]
) / 100.0
# --- Cotisations -----------------------------------------------------
base_ac = min(brut, params["ac_plafond"])
cot_sal = (
brut * params["avs_sal"] / 100.0
+ base_ac * params["ac_sal"] / 100.0
+ anp_sal
+ lpp_sal
)
cot_emp = (
brut * params["avs_emp"] / 100.0
+ base_ac * params["ac_emp"] / 100.0
+ brut * params["af_emp"] / 100.0
+ laa_emp
+ lpp_emp
+ prime_ijm
)
net_avant_impot = brut - cot_sal
cout_employeur = brut + cot_emp + frais
# --- Temps -----------------------------------------------------------
horaire = num(c.get("horaire_hebdo"), 42.0) or 42.0
jours_sem = num(c.get("jours_par_semaine"), 5.0) or 5.0
feries = num(c.get("jours_feries"), 9.0)
vac_sem = num(c.get("vacances_semaines"), 4.0)
vac_jours = vac_sem * jours_sem
jours_ouvrables = params["jours_ouvrables"] - feries
heures_annuelles = horaire * 52.0
heures_effectives = heures_annuelles - vac_sem * horaire - feries * horaire / jours_sem
if heures_effectives <= 0:
heures_effectives = heures_annuelles
heures_effectives_reel = heures_effectives * taux
taux_horaire = brut / heures_effectives_reel if heures_effectives_reel else 0.0
jours_travailles = heures_effectives_reel / (horaire / jours_sem)
jours_libres = vac_jours + feries
valeur_jour = brut / jours_ouvrables if jours_ouvrables else 0.0
# --- Scores qualitatifs ---------------------------------------------
secu = c.get("score_securite")
if secu is None:
secu = 50.0
secu += 15.0 if str(c.get("type", "CDI")).upper().startswith("CDI") else 0.0
secu += 10.0 if num(c.get("delai_conge_mois"), 1) >= 3 else 0.0
secu += 5.0 if num(c.get("delai_conge_mois"), 1) >= 2 else 0.0
secu += 10.0 if num(ijm.get("duree_jours"), 0) >= 720 else 0.0
secu += 5.0 if num(ijm.get("pct"), 100) >= 80 else 0.0
secu += 5.0 if sal_assure > 0 else 0.0
if str(lpp.get("plan", "")).startswith("1e"):
secu -= 10.0
secu = max(0.0, min(100.0, secu))
cond = c.get("score_conditions")
if cond is None:
cond = 50.0
cond += min(20.0, num(c.get("home_office_jours_semaine")) * 10.0)
cond += 10.0 if vac_sem >= 5 else 0.0
cond += 10.0 if num(c.get("horaire_hebdo"), 42) <= 40 else 0.0
cond = max(0.0, min(100.0, cond))
m = {
"id": c.get("id", "?"),
"libelle": c.get("libelle", ""),
"taux": taux,
"fixe_annuel": fixe,
"bonus_espere": bonus_espere,
"brut_annuel": brut,
"frais_annuel": frais,
"cash_annuel": cash,
"salaire_assure": sal_assure,
"lpp_total": lpp_total,
"lpp_emp": lpp_emp,
"lpp_sal": lpp_sal,
"taux_bonif": taux_bonif,
"part_employeur": part_emp * 100.0,
"cot_sal": cot_sal,
"cot_emp": cot_emp,
"net_avant_impot": net_avant_impot,
"cout_employeur": cout_employeur,
"prevoyance_annuelle": lpp_emp + prime_ijm,
"heures_effectives": heures_effectives_reel,
"taux_horaire": taux_horaire,
"jours_libres": jours_libres,
"jours_travailles": jours_travailles,
"valeur_jour": valeur_jour,
"score_securite": secu,
"score_conditions": cond,
}
fac = num(c.get("facteur_indexation"), 1.0)
if fac != 1.0:
for k in MONETARY:
m[k] = m[k] * fac
return m
def legal_flags(c, m, params):
"""Controles de validite. Severites: bloquant / risque / info."""
f = []
age = c.get("age")
vac = num(c.get("vacances_semaines"), 0)
if vac and vac < 4:
f.append(("bloquant", "Vacances < 4 semaines (CO 329a): clause nulle, minimum legal applicable."))
if age is not None and age < 20 and vac and vac < 5:
f.append(("bloquant", "Travailleur de moins de 20 ans: 5 semaines minimum (CO 329a)."))
if num(c.get("periode_essai_mois"), 0) > 3:
f.append(("bloquant", "P\u00e9riode d'essai > 3 mois (CO 335b al. 2)."))
ddc = num(c.get("delai_conge_mois"), 3)
if ddc and ddc < 1:
f.append(("bloquant", "D\u00e9lai de cong\u00e9 < 1 mois (CO 335a al. 2)."))
lpp = c.get("lpp") or {}
if lpp.get("assure", True) is False and m["brut_annuel"] > params["lpp_seuil"]:
f.append(("bloquant", "Non-affiliation LPP alors que le seuil d'entr\u00e9e est d\u00e9pass\u00e9 (LPP 2 et 7)."))
if m["salaire_assure"] > 0 and m["taux_bonif"] + 1e-9 < bonif_minimum(age, params):
f.append(("bloquant", "Bonification LPP sous le minimum d'\u00e2ge (LPP 16)."))
if m["salaire_assure"] > 0 and m["part_employeur"] < 50:
f.append(("bloquant", "Part patronale LPP inf\u00e9rieure \u00e0 la part salariale (LPP 66 al. 1)."))
ijm = c.get("ijm") or {}
if not ijm:
f.append(("risque", "Aucune couverture perte de gain maladie : repli CO 324a seulement."))
if num(ijm.get("pct"), 0) and num(ijm.get("pct")) < 80:
f.append(("risque", "Perte de gain < 80 % : v\u00e9rifier le repli CO 324a (3 semaines / 3 mois \u00e0 100 %)."))
if ijm and num(ijm.get("duree_jours"), 0) < 720:
f.append(("risque", "Couverture maladie < 720 jours : \u00e9cart \u00e0 chiffrer sur 12 mois d'incapacit\u00e9."))
nc = c.get("non_concurrence") or {}
if nc.get("presente"):
if not nc.get("ecrite", False):
f.append(("bloquant", "Non-concurrence non \u00e9crite : nulle (CO 340 al. 1)."))
if num(nc.get("duree_mois"), 0) > 36:
f.append(("bloquant", "Non-concurrence > 3 ans : r\u00e9duite par le juge (CO 340a al. 1)."))
if not nc.get("contrepartie", False):
f.append(("risque", "Non-concurrence sans contrepartie identifiable : validit\u00e9 contestable (CO 340a al. 2)."))
if str(c.get("type", "")).upper().startswith("CDD") and ddc:
f.append(("info", "CDD : le d\u00e9lai de cong\u00e9 contractuel ne s'applique pas de plein droit."))
return f
def packages(metrics, ref_index):
"""Deux packages en CHF, formules explicites, pas de score abstrait.
cash = ce qui tombe sur le compte dans l'annee
package complet = cash + prevoyance patronale
+ (heures de la reference - heures du contrat) x taux horaire de la reference
Le temps est valorise au taux horaire de la reference : c'est le prix auquel on
"rachete" les heures non travaillees. Les jours de vacances, les feries et l'horaire
hebdomadaire sont deja contenus dans les heures effectives : ne pas les compter deux fois.
La securite et les conditions ne sont pas monetisees, elles se lisent dans le tableau.
"""
ref = metrics[ref_index]
out = {"cash (12 mois)": [], "package complet": []}
for m in metrics:
ecart_heures = (ref["heures_effectives"] - m["heures_effectives"]) * ref["taux_horaire"]
out["cash (12 mois)"].append(m["cash_annuel"])
out["package complet"].append(
m["cash_annuel"] + m["prevoyance_annuelle"] + ecart_heures
)
return out
def seuils(metrics, ref_index):
"""CHF/an a ajouter pour egaler la reference sur le package complet."""
ref = metrics[ref_index]
res = []
for m in metrics:
delta = (
(ref["cash_annuel"] - m["cash_annuel"])
+ (ref["prevoyance_annuelle"] - m["prevoyance_annuelle"])
+ (m["heures_effectives"] - ref["heures_effectives"]) * ref["taux_horaire"]
)
res.append(round(delta))
return res
def chf(x, dec=0):
s = f"{x:,.{dec}f}".replace(",", "'")
return s
def render_md(metrics, flags, packs, bascule, ref_id, params):
disq = {cid for cid, items in flags.items() if any(s == "bloquant" for s, _ in items)}
ids = [m["id"] for m in metrics]
entetes = [i + (" \u26a0" if i in disq else "") for i in ids]
L = []
L.append("# Comparatif normalisé de contrats de travail")
L.append("")
L.append(
f"Année de référence : {params['annee']}. Montants en CHF/an. "
f"Taux : AVS/AI/APG {params['avs_sal']} %, AC {params['ac_sal']} % "
f"(plafond {chf(params['ac_plafond'])}), AF {params['af_emp']} % (taux cantonal à vérifier)."
)
if disq:
L.append("")
L.append("\u26a0 = clause bloquante au regard du droit impératif : contrat hors classement.")
L.append("")
L.append("| Indicateur | " + " | ".join(entetes) + " |")
L.append("|---|" + "---|" * len(ids))
rows = [
("Taux d'activité", lambda m: f"{m['taux']*100:.0f} %"),
("Salaire fixe annualisé", lambda m: chf(m["fixe_annuel"])),
("Bonus espéré (pondéré)", lambda m: chf(m["bonus_espere"])),
("**Brut annuel**", lambda m: "**" + chf(m["brut_annuel"]) + "**"),
("Frais forfaitaires", lambda m: chf(m["frais_annuel"])),
("**Cash annuel**", lambda m: "**" + chf(m["cash_annuel"]) + "**"),
("Net avant impôt", lambda m: chf(m["net_avant_impot"])),
("Salaire assuré LPP", lambda m: chf(m["salaire_assure"])),
("Bonification LPP totale", lambda m: f"{m['taux_bonif']:.1f} %"),
("Part patronale LPP", lambda m: f"{m['part_employeur']:.0f} %"),
("**Prévoyance patronale**", lambda m: m["lpp_emp"]),
("**Coût employeur**", lambda m: m["cout_employeur"]),
("Heures effectives / an", lambda m: f"{m['heures_effectives']:.0f} h"),
("**Taux horaire effectif**", lambda m: chf(m["taux_horaire"], 2)),
("Jours libres (vacances + fériés)", lambda m: f"{m['jours_libres']:.0f}"),
("Valeur d'un jour libre", lambda m: chf(m["valeur_jour"])),
("Score sécurité (0-100)", lambda m: f"{m['score_securite']:.0f}"),
("Score conditions (0-100)", lambda m: f"{m['score_conditions']:.0f}"),
]
for label, fn in rows:
if label == "**Prévoyance patronale**":
cells = ["**" + chf(m["prevoyance_annuelle"]) + "**" for m in metrics]
elif label == "**Coût employeur**":
cells = ["**" + chf(m["cout_employeur"]) + "**" for m in metrics]
else:
cells = [fn(m) for m in metrics]
L.append("| " + label + " | " + " | ".join(cells) + " |")
L.append("")
L.append("## \u00c9cart vs " + str(ref_id))
L.append("")
L.append("| Indicateur | " + " | ".join(ids) + " |")
L.append("|---|" + "---|" * len(ids))
ref = metrics[ids.index(ref_id)]
for label, key in [("Cash annuel", "cash_annuel"), ("Pr\u00e9voyance", "prevoyance_annuelle"),
("Co\u00fbt employeur", "cout_employeur"), ("Taux horaire", "taux_horaire")]:
dec = 2 if key == "taux_horaire" else 0
cells = []
for m in metrics:
d = m[key] - ref[key]
pct = (d / ref[key] * 100.0) if ref[key] else 0.0
cells.append(f"{d:+,.{dec}f} ({pct:+.1f} %)".replace(",", "'"))
L.append(f"| {label} | " + " | ".join(cells) + " |")
L.append("")
L.append("## Packages annualisés en CHF (h.c. = hors classement)")
L.append("")
L.append(
"Cash = ce qui tombe sur le compte. Package complet = cash + prévoyance patronale "
f"+ heures non travaillées valorisées au taux horaire de {ref_id}."
)
L.append("")
L.append("| Package | " + " | ".join(entetes) + " |")
L.append("|---|" + "---|" * len(ids))
for nom, vals in packs.items():
eligibles = [v for i, v in enumerate(vals) if ids[i] not in disq]
best = max(eligibles) if eligibles else None
cells = []
for i, v in enumerate(vals):
txt = chf(v)
if ids[i] in disq:
cells.append(txt + " (h.c.)")
elif v == best:
cells.append("**" + txt + "**")
else:
cells.append(txt)
L.append(f"| {nom} | " + " | ".join(cells) + " |")
L.append("")
L.append(
"Sécurité et conditions ne sont pas monétisées : elles se lisent dans le tableau "
"ci-dessus et dans les points de validité. Un contrat h.c. ne peut pas gagner."
)
L.append("")
L.append("## Seuil de bascule (CHF/an à ajouter pour égaler " + str(ref_id) + " sur le package complet)")
L.append("")
for i, m in enumerate(metrics):
if m["id"] == ref_id:
L.append(f"- {m['id']} : r\u00e9f\u00e9rence")
else:
s = bascule[i]
if s > 0:
L.append(f"- {m['id']} : +{chf(s)} CHF/an")
else:
L.append(f"- {m['id']} : d\u00e9j\u00e0 {chf(-s)} CHF/an au-dessus de la r\u00e9f\u00e9rence")
if flags:
L.append("")
L.append("## Points de validit\u00e9")
L.append("")
for cid, items in flags.items():
if not items:
continue
L.append(f"**{cid}**")
for sev, msg in items:
L.append(f"- [{sev}] {msg}")
L.append("")
return "\n".join(L)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("fichier")
ap.add_argument("--md")
ap.add_argument("--json")
ap.add_argument("--reference")
ap.add_argument("--annee", type=int)
args = ap.parse_args()
with open(args.fichier, encoding="utf-8") as fh:
data = json.load(fh)
params = dict(DEFAULTS)
params.update(data.get("parametres") or {})
if args.annee:
params["annee"] = args.annee
contrats = data.get("contrats") or []
if len(contrats) < 2:
print("Il faut au moins deux contrats pour comparer.", file=sys.stderr)
sys.exit(1)
metrics = [compute(c, params) for c in contrats]
flags = {c["id"]: legal_flags(c, m, params) for c, m in zip(contrats, metrics)}
ref_id = args.reference or metrics[0]["id"]
if ref_id not in [m["id"] for m in metrics]:
print(f"R\u00e9f\u00e9rence inconnue : {ref_id}", file=sys.stderr)
sys.exit(1)
ref_index = [m["id"] for m in metrics].index(ref_id)
packs = packages(metrics, ref_index)
bascule = seuils(metrics, ref_index)
md = render_md(metrics, flags, packs, bascule, ref_id, params)
print(md)
if args.md:
with open(args.md, "w", encoding="utf-8") as fh:
fh.write(md + "\n")
if args.json:
payload = {
"parametres": params,
"contrats": [dict(m, points=flags[m["id"]]) for m in metrics],
"packages": {k: dict(zip([m["id"] for m in metrics], v)) for k, v in packs.items()},
"seuil_bascule": dict(zip([m["id"] for m in metrics], bascule)),
"reference": ref_id,
}
with open(args.json, "w", encoding="utf-8") as fh:
json.dump(payload, fh, ensure_ascii=False, indent=2)
if __name__ == "__main__":
main()