misc-cazino/cr-reunion/run.py

510 lines
18 KiB
Python

'''
Usage :
wcs-manage runscript -d wcs.dev.publik.love cr-reunion/run.py import --filepath ~/notes/clients/cr-reunion/aide-voyage/demande.csv --api-url=https://wcs.dev.publik.love/api/ --key=cr-reunion-voyage --passwd=29a7182f-e3f0-4044-b6d4-163d16bbb86e
wcs-manage runscript -d wcs.dev.publik.love cr-reunion/run.py reset
'''
import argparse
import base64
import collections
import csv
import datetime
import hashlib
import hmac
import random
import time
import urllib.parse
from pprint import pprint
import requests
from requests.auth import HTTPBasicAuth
from wcs.carddef import CardDef
import wcs.sql_criterias as st
CARD_BENEFICIAIRE_SLUG = 'beneficiaire-ct'
CARD_BON_SLUG = 'bon-ct'
CARD_COMPAGNIE_AERIENNE_SLUG = 'cta-referentiel-compagnies-aeriennes'
CARD_DEROGATION_SLUG = 'referentiel-beneficiaires-de-derogation'
CARD_STATUT_BON_SLUG = 'ct-statuts-des-bons'
CARD_TYPE_AIDE_SLUG = 'referentiel-type-d-aide'
BENEFICIAIRES = {}
COMPAGNIES_AERIENNES = {}
DEROGATIONS = {}
STATUT_BON = {}
TYPE_AIDES = {}
def build_cache(slug, cache):
cardef = CardDef.get_by_urlname(slug)
libelle_field = get_field_by_varname(cardef, 'libelle')
for card in cardef.data_class().select():
cache[card.data[libelle_field.id].upper()] = str(card.id)
def get_beneficiaire_hash(prenom, nom, numero_fiscal):
return '%s##%s##%s' % (prenom.strip().upper(), nom.strip().upper(), numero_fiscal)
def get_beneficiaire_hash_from_row(row):
return get_beneficiaire_hash(row['prenom'], row['nom'], row['numero fiscal'])
def get_field_by_varname(cardef, varname):
for field in cardef.fields:
if field.varname == varname:
return field
raise ValueError('Missing field: %s' % varname)
def get_type_aide_cardddata(label):
if not TYPE_AIDES:
build_cache(CARD_TYPE_AIDE_SLUG, TYPE_AIDES)
return TYPE_AIDES[label.upper()]
def get_derogation_cardddata(label):
if not DEROGATIONS:
build_cache(CARD_DEROGATION_SLUG, DEROGATIONS)
return DEROGATIONS[label.upper()]
def get_statut_bon_cardddata(label):
if not STATUT_BON:
build_cache(CARD_STATUT_BON_SLUG, STATUT_BON)
return STATUT_BON[label.upper()]
def get_compagnie_aerienne(label):
if not COMPAGNIES_AERIENNES:
build_cache(CARD_COMPAGNIE_AERIENNE_SLUG, COMPAGNIES_AERIENNES)
if label.upper() not in COMPAGNIES_AERIENNES:
print('missing company : %s' % label.upper())
return ''
return COMPAGNIES_AERIENNES[label.upper()]
def parse_date(date_str, date_format='%Y-%m-%d'):
if not date_str or date_str == 'NULL':
return ''
return datetime.datetime.strptime(date_str, date_format).date().strftime('%Y-%m-%d')
def api_call(args, path, data=None, user_email=None, method='post'):
basic = HTTPBasicAuth(args.key, args.passwd)
url = args.api_url + path
if method == 'post':
post_data = {'data': data}
if user_email:
post_data['user'] = {'email': user_email}
resp = requests.post(url, json=post_data, auth=basic)
elif method == 'get':
resp = requests.get(url, auth=basic)
resp.raise_for_status()
if resp.json()['err'] != 0:
raise ValueError()
return resp
def get_rows(args):
for filepath in args.filepath:
with open(filepath) as csvfile:
reader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
numline = 0
for i, row in enumerate(reader):
if i < args.start_line:
continue
if args.mode != 'full' and numline > args.sample_numlines:
break
numline += 1
yield i, row
def import_data(args):
benef_path = 'cards/%s/submit' % CARD_BENEFICIAIRE_SLUG
bon_path = 'cards/%s/submit' % CARD_BON_SLUG
# list of (criteria, result) tuples
type_aide_mapping = [
(
{
'volet': 'VOLET B - Métropole / Réunion',
'type demande': 'BON',
'type aide': 'RESSOURCEMENT ETUDIANT',
'mesure specifique': 'NULL',
},
{
'bon_type_aide': get_type_aide_cardddata('Ressourcement étudiant')
},
),
(
{
'volet': 'VOLET B - Métropole / Réunion',
'type demande': 'REMBOURSEMENT',
'type aide': 'RESSOURCEMENT ETUDIANT',
'mesure specifique': 'NULL',
},
{
'bon_type_aide': get_type_aide_cardddata('Ressourcement étudiant')
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'REMBOURSEMENT',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Cas particulier du deuil en Métropole',
},
{'bon_type_aide': get_type_aide_cardddata('Remboursement (deuil)')}
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'GRAND PUBLIC',
'mesure specifique': 'NULL',
},
{
'bon_type_aide': get_type_aide_cardddata('Grand Public'),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'CONTINUITE FUNERAIRE',
'mesure specifique': 'NULL',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Continuité Territoriale Funéraire'
)
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Voyage pédagogique pour les publics scolaires',
},
{
'bon_type_aide': get_type_aide_cardddata('Voyage pédagogique (élève)'),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Accompagnateur voyage pédagogique pour les publics scolaires mineurs.',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Voyage pédagogique (accompagnateur)'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Sportif de haut niveau national ou régional',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Compétition sportive (sportif)'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Accompagnateur de sportif(s) de haut niveau',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Compétition sportive (accompagnateur)'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': "Accompagnateur majeur d'un jeune de moins de 26 ans pour un premier départ de La Réunion pour les études, ou lycéen ou apprenti.",
},
{
'bon_type_aide': get_type_aide_cardddata(
"Accompagnateur d'un jeune de moins de 26 ans pour un premier départ de La Réunion"
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Lycéens, apprenti, étudiant en France métropolitaine',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Lycéen, apprenti, étudiant en France métropolitaine'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Acteurs culturels',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Voyage artistique (artistes et acteurs culturels)'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Accompagnateur acteurs culturels',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Voyage artistique (accompagnateur)'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': "Patient lors d'un rapatriement sanitaire pris en charge à 65% maximum par la CGSS après réponse négative des organismes sollicités",
},
{
'bon_type_aide': get_type_aide_cardddata(
'Patient en transfert sanitaire'
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': "Accompagnateur d'un patient lors d'un rapatriement sanitaire pris en charge à 65% maximum par la CGSS après réponse négative des organismes sollicités",
},
{
'bon_type_aide': get_type_aide_cardddata(
"Accompagnateur d'un patient en transfert sanitaire"
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Doctorant et post-doctorant pour des travaux de recherche',
},
{
'bon_type_aide': get_type_aide_cardddata('Doctorant ou post-doctorant'),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': "Salarié non fonctionnaire ou demandeur d'emploi non aidé par d'autres organismes",
},
{
'bon_type_aide': get_type_aide_cardddata(
"Épreuves d'admissibilité ou d'admission de concours de la fonction publique"
),
},
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'MESURES SPECIFIQUES',
'mesure specifique': 'Cas particulier du deuil en Métropole',
},
{
'bon_type_aide': get_type_aide_cardddata(
'Deuil et raisons funéraires (hors CTF et remboursement)'
),
},
),
(
{
'volet': 'VOLET B - Métropole / Réunion',
'type demande': 'BON',
'type aide': 'AIDE ETUDIANT SPECIAL COVID',
'mesure specifique': 'NULL',
},
'continue',
),
(
{
'volet': 'VOLET A - Réunion / Métropole',
'type demande': 'BON',
'type aide': 'GRAND PUBLIC',
'derogation': "Pupilles de la nation ou enfants faisant l'objet d'un placement auprès des structures spécifiques",
},
{
'bon_type_aide': get_type_aide_cardddata('Pupille de la nation'),
},
),
]
derogation_mapping = {
"Personne porteuse d'un handicap": get_derogation_cardddata('Personne en situation de handicap'),
'Accompagnateur femme enceinte': get_derogation_cardddata("Accompagnateur d'une femme enceinte"),
'Accompagnateur personne âgée': get_derogation_cardddata("Accompagnateur d'une personne âgée"),
"Accompagnateur personne porteuse d'un handicap": get_derogation_cardddata("Accompagnateur d'une personne en situation de handicap"),
'Femme enceinte': get_derogation_cardddata('Femme enceinte'),
'Personne âgée': get_derogation_cardddata('Personne âgée'),
"Enfants nés après la déclaration fiscale et n'apparaissant par sur l'avis fiscal de référence": '',
"Pupilles de la nation ou enfants faisant l'objet d'un placement auprès des structures spécifiques": '',
'Personnes ne pouvant justifier de leur état civil à La Réunion': '',
'NULL': ''
}
status_mapping = {
'CORRIGÉ': get_statut_bon_cardddata('Vérifiable'),
'VALIDÉ': get_statut_bon_cardddata('Facturable'),
'MANDATÉ': get_statut_bon_cardddata('En cours de mandatement'),
'RESERVÉ': get_statut_bon_cardddata('Réservé'),
'REFUSÉ': get_statut_bon_cardddata('Invalide'),
'PAYÉ': get_statut_bon_cardddata('Payé'),
'MANDATEMENT EN COURS': get_statut_bon_cardddata('En cours de mandatement'),
'PRÉPARATION MANDAT': get_statut_bon_cardddata('En cours de mandatement'),
}
def get_from_mapping(row_data, mapping):
for criteria, extra_data in mapping:
for key, value in criteria.items():
if row_data[key] != value:
break
else:
return extra_data
return None
# Initialize BENEFICIAIRES with carddata
beneficiaire_carddef = CardDef.get_by_urlname(CARD_BENEFICIAIRE_SLUG)
prenom_field = get_field_by_varname(beneficiaire_carddef, 'prenom')
nom_field = get_field_by_varname(beneficiaire_carddef, 'nom_usage')
numero_fiscal_field = get_field_by_varname(beneficiaire_carddef, 'numero_fiscal')
for beneficiaire in beneficiaire_carddef.data_class().select():
BENEFICIAIRES[get_beneficiaire_hash(beneficiaire.data[prenom_field.id], beneficiaire.data[nom_field.id], beneficiaire.data[numero_fiscal_field.id])] = str(beneficiaire.id)
for i, row in get_rows(args):
type_aide = get_from_mapping(row, type_aide_mapping)
if type_aide is None:
print('No matching criteria for type_aide in row num %s.' % i)
pprint(row)
continue
if type_aide == 'continue':
continue
if row['derogation'] not in derogation_mapping:
print('No matching derogation matching in row num %s.' % i)
pprint(row)
continue
derogation = derogation_mapping[row['derogation']]
beneficiaire_hash = get_beneficiaire_hash_from_row(row)
if beneficiaire_hash not in BENEFICIAIRES:
email = ''
if row['email'] != 'NULL':
email = row['email'].lower()
beneficiaire_data = {
'courriel_demandeur': email,
'date_import': datetime.datetime.now().date().strftime('%Y-%m-%d'),
'date_naissance': parse_date(row['date naissance']),
'nom_naissance': row['nom naissance'].upper(),
'nom_usage': row['nom'].upper(),
'numero_fiscal': row['numero fiscal'],
'prenom': row['prenom'].upper(),
}
resp = api_call(args, path=benef_path, data=beneficiaire_data)
beneficiaire_id = str(resp.json()['data']['id'])
BENEFICIAIRES[beneficiaire_hash] = beneficiaire_id
beneficiaire_id = BENEFICIAIRES[beneficiaire_hash]
email = ''
if row['email'] != 'NULL':
email = row['email'].lower()
data = {
'annee_demande': row['campagne'],
'beneficiaire': beneficiaire_id,
'beneficiaire_courriel': email,
'beneficiaire_numero_cni': row['numero CI/passeport'],
'bon_numero': row['numero bon'],
'bon_montant': row['montant region'],
'bon_statut': status_mapping[row['statut']],
'bon_type_derogation': derogation,
'bon_validite': row['date validite'],
'commentaires_import': 'Trajet : %s.' % row['trajet'],
'date_import': datetime.datetime.now().date().strftime('%Y-%m-%d'),
'voyage_date_aller': parse_date(row['date aller'], '%Y-%m-%d %H:%M:%S'),
'voyage_date_retour': parse_date(row['date retour'], '%Y-%m-%d %H:%M:%S'),
}
if row['compagnie'] and row['compagnie'] != 'NULL':
data['billet_compagnie'] = get_compagnie_aerienne(row['compagnie'])
data.update(type_aide)
resp = api_call(args, path=bon_path, data=data)
def reset(args):
for slug in (CARD_BENEFICIAIRE_SLUG, CARD_BON_SLUG):
cardef = CardDef.get_by_urlname(slug)
date_timport_field = get_field_by_varname(cardef, 'date_import')
for carddata in cardef.data_class().select(
[st.NotNull(st.get_field_id(date_timport_field))]
):
carddata.remove_self()
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_import = subparsers.add_parser('import')
parser_import.set_defaults(func=import_data)
parser_import.add_argument('--filepath', nargs='+')
parser_import.add_argument('--mode', default='sample', choices=('sample', 'full'))
parser_import.add_argument('--sample-numlines', default=100, type=int)
parser_import.add_argument('--start-line', default=0, type=int)
parser_import.add_argument('--api-url')
parser_import.add_argument('--email')
parser_import.add_argument('--key')
parser_import.add_argument('--passwd')
parser_reset = subparsers.add_parser('reset')
parser_reset.set_defaults(func=reset)
args = parser.parse_args()
args.func(args)