Directly call the API

This commit is contained in:
Denis Merigoux 2023-11-21 12:15:36 +01:00
parent 9b287cc0a5
commit d2e4da144c
No known key found for this signature in database
GPG Key ID: EE99DCFA365C3EE3
4 changed files with 179 additions and 211 deletions

View File

@ -0,0 +1,177 @@
# This code is ported from https://github.com/jboillot/apl-fetcher
from .input import AppartementOuMaison, AppartementOuMaisonType, CnafSimulatorInput, LogementCrousType, LogementCrous, SeulOuCouple
import json
import requests # type: ignore
def get_bearer():
url = "https://wwwd.caf.fr/wps/s/GenerateTokenJwtPublic/"
payload = {}
headers = {
'Accept': 'application/json, text/plain, */*',
'Connection': 'keep-alive',
'Referer': 'https://wwwd.caf.fr/wps/portal/caffr/aidesetdemarches/mesdemarches/faireunesimulation/lelogement',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'Cookie': 'DYN_PERSYS=438624522.14119.0000; PERSYS=2801671596.47138.0000; TS015047ff=015e43680bb2f0a3e2996d95996eaa92a52fbad1221c1d64a9f10e25a1c12f8c7ec932a9aff3ba4fd7bdd4441e48f788df0b32610f'
}
response = requests.request("GET", url, headers=headers, data=payload)
data = json.loads(response.text)
return data['cnafTokenJwt']
def get_simulation(bearer, payload_json):
import requests
url = "https://wwwd.caf.fr/api/prestationalmiddle/v1/simuler"
payload = json.dumps(payload_json)
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8,fr;q=0.7',
'Authorization': bearer,
'Connection': 'keep-alive',
'Content-Type': 'application/json; charset=UTF-8',
'Origin': 'https://wwwd.caf.fr',
'Referer': 'https://wwwd.caf.fr/wps/portal/caffr/aidesetdemarches/mesdemarches/faireunesimulation/lelogement',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
# 'Cookie': 'DYN_PERSYS=438624522.14119.0000; PERSYS=2801671596.47138.0000; TS015047ff=015e43680bb2f0a3e2996d95996eaa92a52fbad1221c1d64a9f10e25a1c12f8c7ec932a9aff3ba4fd7bdd4441e48f788df0b32610f'
}
response = requests.request("POST", url, headers=headers, data=payload)
response_json = response.json()
print(response_json)
if response_json['possedeDesDroits']:
return int(response_json['montantDroit'])
else:
return 0
def format_payload(input: CnafSimulatorInput):
# C'est un exemple de fichier attendu
# Les variables parlent d'elles mêmes et ont un type contraint
flgLogementConventionne = False
if isinstance(input.logement, LogementCrous):
if input.logement.typ_v == LogementCrousType.Chambre_rehabilitee:
flgLogementConventionne = False
if input.logement.meublee() is None:
flgMeubleLogement = False
else:
flgMeubleLogement = input.logement.meublee()
logement = {
"adresse": {
"codeInsee": str(input.zone.value)
},
"loyer": {
"mtLoyerFoyer": float(input.loyer)
},
# parmi ["APPARTEMENT_OU_MAISON", "FOYER", "LOGEMENT_CROUS",
"cdTypeLogement": str(input.logement.residence_v),
# "MAISON_RETRAITE_EHPAD", "RESIDENCE_SOCIALE_FJT", "CHAMBRE"]
"flgLogementConventionne": flgLogementConventionne,
"flgMeubleLogement": flgMeubleLogement
}
personne1 = {
"personne": {
"enActivite": False,
"estAuChomage": False,
"flgDemandeur": True, # Est ce la personne demandeuse ?
"situationProfessionnelleList": [
{
"situationProfessionnelle": "ACT_INCONNUE"
# parmi "ETU" (étudiant non boursier)
# "CONGE_MATPAT",
# "ST_RSA",
# "EBO" (étudiant boursier)
# "ACT_INCONNUE" (autre situation)
# Et pour les enfants ça peut être
# "ETU"
# "APPRENTI"
# "SALARIE_CP"
}
],
"ressourceList": [
{
"groupeOrigine": "salaire",
# parmi "salaire", "allocation_chomage",
# "retraite_pension_imposable", "pension_alimentaire_recue"
# "revenu_travailleur_independant"
"mtRessource": int(float(input.revenu_pris_en_compte) / 0.9)
},
]
}
}
household_members = [personne1]
if input.seul_ou_couple == SeulOuCouple.Seul:
personne2 = None
else:
personne2 = {
"personne": {
"flgDemandeur": False,
"situationProfessionnelleList": [
{
"situationProfessionnelle": "ACT_INCONNUE"
}
],
"ressourceList": []
}
}
household_members.append(personne2)
children = [
{
"role": "ENF",
"personne": {
"role": "ENF",
"enfantACharge": True,
"flgDemandeur": False,
"situationProfessionnelleList": [
{
"situationProfessionnelle": "ETU"
}
],
"ressourceList": []
}
}
for enfant in input.enfants
]
household_members.extend(children)
flgColocation = False
if isinstance(logement, AppartementOuMaison):
if logement.typ_v == AppartementOuMaisonType.Colocation:
flgColocation = True
result = {
"rattachementLogementList": [
{
"flgColocation": flgColocation,
"logement": logement
}
],
"compoFoyerList": household_members,
"situationFamilialeList": [
{
"estEnCouple": True if input.seul_ou_couple == SeulOuCouple.EnCouple else False,
"situationFamille": str(input.seul_ou_couple.value)
# Parmi "CEL" (célibataire)
# "VIM" (vie maritale = couple)
}
]}
return result
def run_simulator(input: CnafSimulatorInput) -> int:
payload = format_payload(input)
print(payload)
bearer = get_bearer()
return (get_simulation(bearer, payload))

View File

@ -1,6 +1,6 @@
from .random_input_generator import generate_random_input
from .input import AppartementOuMaison, AppartementOuMaisonType, CnafSimulatorInput, Enfant, SeulOuCouple, Zone
from .pupeteer import run_simulator
from .call_cnaf import run_simulator
from .cnaf_to_catala import run_catala_by_converting_cnaf_input
# input identical to the JS test of the housing benefits

View File

@ -1,209 +0,0 @@
# This code is ported from https://github.com/jboillot/apl-fetcher
from pdb import runeval
from playwright.sync_api import sync_playwright
from .input import AppartementOuMaison, AppartementOuMaisonType, CnafSimulatorInput, Enfant, LogementCrous, SeulOuCouple
import re
HOME_PAGE = 'https://wwwd.caf.fr/wps/portal/caffr/aidesetservices/lesservicesenligne/estimervosdroits/lelogement'
def run_simulator(input: CnafSimulatorInput) -> int:
with sync_playwright() as p:
browser = p.firefox.launch(headless=False, slow_mo=100)
page = browser.new_page()
# Go to the CNAF simulator
page.goto(HOME_PAGE)
# Click on cookie banner
cookie_button = page.wait_for_selector(
"div[id=\"popup-accept-cookies\"] >> button")
if cookie_button is None:
raise RuntimeError
cookie_button.click(force=True)
# Click on 'Commencer'
commencer_button = page.wait_for_selector('button[id="btn-suivant"]')
if commencer_button is None:
raise RuntimeError
commencer_button.click(force=True)
# Code Postal
code_postal_input = page.wait_for_selector('input[id="cpCommune"]')
if code_postal_input is None:
raise RuntimeError
code_postal_input.fill(input.zone.value)
# Select first match
page.wait_for_selector('a[class="ng-tns-c31-0"]')
page.keyboard.press('Enter')
# Select residence type
type_residence_button = page.wait_for_selector(
"input[id=\"lieuResidence_{}\"]".format(input.logement.residence()))
if type_residence_button is None:
raise RuntimeError
type_residence_button.click(force=True)
# Select housing type for some things
if isinstance(input.logement, AppartementOuMaison):
type_appartement_ou_maison_button = page.wait_for_selector(
"input[id=\"typeOccupation_{}\"]".format(input.logement.typ()))
if type_appartement_ou_maison_button is None:
raise RuntimeError
type_appartement_ou_maison_button.click(force=True)
elif isinstance(input.logement, LogementCrous):
type_crous_button = page.wait_for_selector(
"input[id=\"typeCrous_{}\"]".format(input.logement.typ()))
if type_crous_button is None:
raise RuntimeError
type_crous_button.click(force=True)
# Is the location meublee
if not (input.logement.meublee() is None):
meuble_button = page.wait_for_selector(
"input[id=\"estMeuble_{}\"]".format("true" if input.logement.meublee() else "false"))
if meuble_button is None:
raise RuntimeError
meuble_button.click(force=True)
# Monthly rent
loyer_mensuel_input = page.wait_for_selector(
'input[id="mttDeclare"]')
if loyer_mensuel_input is None:
raise RuntimeError
loyer_mensuel_input.fill("{}".format(input.loyer))
# Couple or not
seul_button = page.wait_for_selector(
"input[id=\"situationFamiliale_{}\"]".format(input.seul_ou_couple.value))
if seul_button is None:
raise RuntimeError
seul_button.click(force=True)
# Number of children
if len(input.enfants) < 0 or len(input.enfants) > 20:
raise RuntimeError
plus_button = page.wait_for_selector('button:has-text("+")')
if plus_button is None:
raise RuntimeError
for i in range(len(input.enfants)):
plus_button.click(force=True)
# Main applicant salary
salaires_button = page.wait_for_selector(
"button[id=\"SALAIRE_0_checkbox\"]")
if salaires_button is None:
raise RuntimeError
salaires_button.click(force=True)
salaires_input = page.wait_for_selector(
"input[id=\"SALAIRE_0_montant\"]")
if salaires_input is None:
raise RuntimeError
salaires_input.fill("{}".format(
int(float(input.revenu_pris_en_compte) / 0.9)))
salaires_input.wait_for_element_state(state="stable")
# We divide by 0.9 because salaries have a 10% franchise
# Now if the salary is too low you have to click another button
status_selector = "input[id=\"sitro_0_SITPRO_ACT_INCONNUE\"]"
if not (page.query_selector is None):
status_button = page.wait_for_selector(status_selector)
if status_button is None:
raise RuntimeError
status_button.click(force=True)
# If there is a partner we assume no income for simplicity
if input.seul_ou_couple == SeulOuCouple.EnCouple:
sans_button = page.wait_for_selector(
"button[id=\"aucunRevenu_1\"]")
if sans_button is None:
raise RuntimeError
sans_button.click(force=True)
status_button = page.wait_for_selector(
"input[id=\"sitro_1_SITPRO_ACT_INCONNUE\"]")
if status_button is None:
raise RuntimeError
status_button.click(force=True)
# Continue
continuer_button = page.wait_for_selector('button[id="btn-suivant"]')
if continuer_button is None:
raise RuntimeError
continuer_button.click(force=True)
# Extra questions about children
if len(input.enfants) > 0:
for i in range(len(input.enfants)):
if input.enfants[i].age < 21:
yes_button = page.wait_for_selector(
"input[id=\"QUESTION_AGE_MAX_{}_true\"]".format(i))
if yes_button is None:
raise RuntimeError
yes_button.click(force=True)
else:
no_button = page.wait_for_selector(
"input[id=\"QUESTION_AGE_MAX_{}_false\"]".format(i))
if no_button is None:
raise RuntimeError
no_button.click(force=True)
if int(float(input.enfants[i].remuneration_derniere_annee) / 0.9) > 4500:
yes_button = page.wait_for_selector(
"input[id=\"QUESTION_REVENUS_DERNIERSMOIS_{}_true\"]".format(i))
if yes_button is None:
raise RuntimeError
yes_button.click(force=True)
# We have to provide the exact remuneration
salaires_button = page.wait_for_selector(
"button[id=\"salaire{}_checkbox\"]".format(i))
if salaires_button is None:
raise RuntimeError
salaires_button.click(force=True)
salaires_input = page.wait_for_selector(
"input[id=\"MONTANT_salaire{}\"]".format(i))
if salaires_input is None:
raise RuntimeError
salaires_input.fill("{}".format(
int(float(input.enfants[i].remuneration_derniere_annee) / 0.9)))
salaires_status = page.wait_for_selector(
"input[id=\"QUESTION_SITPRO_{}_SALARIE_CP\"]".format(i))
if salaires_status is None:
raise RuntimeError
salaires_status.click(force=True)
else:
no_button = page.wait_for_selector(
"input[id=\"QUESTION_REVENUS_DERNIERSMOIS_{}_false\"]".format(i))
if no_button is None:
raise RuntimeError
no_button.click(force=True)
# Continue
continuer_button = page.wait_for_selector(
'button[id="btn-suivant"]')
if continuer_button is None:
raise RuntimeError
continuer_button.click(force=True)
# Wait for result page
page.wait_for_selector("section[id=\"resultat\"]")
# Retrieve the amount
result = page.query_selector('text=/\\d+ € par mois/')
if result is None:
# Then no benefits!
housing_benefits = 0
else:
result_text = result.text_content()
if result_text is None:
housing_benefits = 0
else:
match = re.search("(\\d+) € par mois", result_text)
if match is None:
raise RuntimeError
housing_benefits = int(match.group(1))
if housing_benefits is None:
raise RuntimeError
browser.close()
return housing_benefits

View File

@ -17,7 +17,7 @@ def generate_random_input() -> CnafSimulatorInput:
zone = Zone.Zone2
else: # zone_i == 3
zone = Zone.Zone3
loyer = random.randint(300, 1800)
loyer = random.randint(300, 1000)
revenus_pris_en_compte = random.randint(0, 200) * 100
seul_ou_couple_i = random.randint(1, 2)
if seul_ou_couple_i == 1: