<commit_before>.. doctest docs/specs/clients.rst

.. _welfare.clients.parameters:
.. _welfare.specs.clients:

=================
Filtering clients
=================

This document describes and tests some ways of filtering clients.

Most code is in :mod:`lino_welfare.modlib.pcsw` plugin.


.. contents::
   :depth: 2
   :local:

.. include:: /../docs/shared/include/tested.rst

>>> from lino import startup
>>> startup('lino_welfare.projects.gerd.settings.doctests')
>>> from lino.api.doctest import *

>>> ClientEvents = pcsw.ClientEvents
>>> ses = rt.login("hubert")


Default lists of coached clients
================================

>>> ses.show(pcsw.CoachedClients, column_names="name_column")
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=============================
 Name
-----------------------------
 <NAME> (116)
 <NAME> (177)
 <NAME> (118)
 <NAME> (124)
 <NAME> (179)
 <NAME> (128)
 EMONTS-GAST Erna (152)
 <NAME> (129)
 <NAME> (127)
 GROTECLAES Gregory (132)
 <NAME> (133)
 <NAME> (137)
 <NAME> (181)
 <NAME> (139)
 <NAME> (141)
 <NAME> (178)
 <NAME> (142)
 LAZARUS Line (144)
 <NAME> (146)
 <NAME> (147)
 <NAME> (153)
 RADERMACHER Christian (155)
 <NAME> (157)
 <NAME> (159)
 <NAME> (161)
 <NAME> (173)
 <NAME> (165)
 <NAME> (166)
 ÖSTGES Otto (168)
=============================
<BLANKLINE>

>>> ses.show(integ.Clients, column_names="name_column")
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
==========================
 Name
--------------------------
 <NAME> (177)
 <NAME> (118)
 <NAME> (128)
 <NAME> (152)
 <NAME> (127)
 <NAME> (133)
 <NAME> (137)
 <NAME> (181)
 <NAME> (139)
 <NAME> (142)
 <NAME> (146)
 <NAME> (147)
 <NAME> (157)
 <NAME> (161)
 <NAME> (173)
 <NAME> (166)
 ÖSTGES Otto (168)
==========================
<BLANKLINE>



Filtering clients about their coachings
=======================================

>>> translation.activate('en')

The demo database contains 13 clients whe meet the following
conditions:

- the client_state is "Coached"
- client has more than 2 coachings
- at least one of these coachings has been ended

>>> from django.db.models import Count
>>> qs = pcsw.Client.objects.filter(client_state=pcsw.ClientStates.coached)
>>> qs = qs.annotate(coachings_count=Count('coachings_by_client'))
>>> qs = qs.filter(coachings_count__gt=2)
>>> qs = qs.filter(coachings_by_client__end_date__isnull=False)
>>> qs = qs.order_by('id')
>>> qs.count()
13
>>> [obj.pk for obj in qs]
[116, 127, 129, 133, 139, 144, 147, 155, 159, 166, 173, 179, 181]

Let's look at some of these clients more in detail.

>>> def show_coachings(pk):
...     obj = pcsw.Client.objects.get(pk=pk)
...     ses.show('coachings.CoachingsByClient', master_instance=obj, column_names="start_date end_date user primary", header_level=4)

>>> show_coachings(179)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of DUBOIS Robin (179) (Is active)
===========================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   Yes
 08/03/2013     04/10/2013   <NAME>   No
 04/10/2013                  <NAME>    No
============== ============ ================= =========
<BLANKLINE>

We can see that Robin is currently coached by Alica and Mélanie, and
that Hubert stopped coaching him on 04.10.2013:


Same view for some other clients:

>>> show_coachings(116)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of AUSDEMWALD Alfons (116) (Is active)
================================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   No
 13/03/2012     08/03/2013   <NAME>   No
 08/03/2013     24/10/2013   <NAME>    No
 24/10/2013                  <NAME>   Yes
============== ============ ================= =========
<BLANKLINE>


>>> show_coachings(166)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of VAN VEEN Vincent (166) (Is active)
===============================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   Yes
 08/03/2013     04/10/2013   <NAME>    No
 04/10/2013                  <NAME>   No
============== ============ ================= =========
<BLANKLINE>


>>> print(dd.fds(dd.today()))
22/05/2014

When Mélanie opens her :menuselection:`Integration --> Clients` list,
then she sees the following clients:

>>> ses = rt.login('melanie')
>>> ses.show(integ.Clients, column_names="name_column", header_level=4)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
Integration Clients (Coached, Coached by <NAME>)
========================================================
=============================
 Name
-----------------------------
 <NAME> (177)
 <NAME> (118)
 <NAME> (124)
 <NAME> (179)
 <NAME> (128)
 <NAME> (129)
 GROTECLAES Gregory (132)
 <NAME> (137)
 <NAME> (141)
 <NAME> (178)
 LAZARUS Line (144)
 <NAME> (146)
 <NAME> (147)
 <NAME> (153)
 <NAME> (155)
 <NAME> (157)
 RADERMACHER Guido (159)
 <NAME> (161)
 ÖSTGES Otto (168)
=============================
<BLANKLINE>

Robin is there, but Alfons and Vincent aren't.

Here is a list of Mélanies clients on 2013-10-01.  Mélanie can get
this by manually filling that date into the
:attr:`lino_welfare.modlib.pcsw.Clients.end_date` parameter field.

>>> pv = dict(end_date=i2d(20131001))
>>> ses.show(integ.Clients, column_names="name_column", param_values=pv, header_level=4)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
Integration Clients (Coached, Coached by <NAME>)
========================================================
=========================
 Name
-------------------------
 <NAME> (116)
 <NAME> (129)
 <NAME> (127)
 <NAME> (133)
 LAZARUS Line (144)
 <NAME> (159)
 <NAME> (166)
=========================
<BLANKLINE>


Note that

- Robin is *not* included since Mélanie started coaching him only
  later.
- Vincent *is* now included since Mélanie was coaching him then.



Filtering clients about their notes
===================================


>>> ses = rt.login('robin')

Coached clients who have at least one note:

>>> pv = dict(observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
============================
 Name
----------------------------
 <NAME> (116)
 <NAME> (118)
 <NAME> (124)
============================
<BLANKLINE>

All clients who have at least one note:

>>> pv = dict(client_state=None, observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================================
 Name
-----------------------------------------
 <NAME> (116)
 <NAME> (117)
 <NAME> (118)
 <NAME> (122)
 <NAME> (121)
 <NAME> (124)
 <NAME> (123)
 <NAME> (125)
 <NAME> (126)
=========================================
<BLANKLINE>


Coached clients who have at least one note dated 2013-07-25 or later:

>>> pv = dict(start_date=i2d(20130725), observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================
 Name
-------------------------
 <NAME> (116)
=========================
<BLANKLINE>

.. show the SQL when debugging:
    >>> # ar = ses.spawn(pcsw.CoachedClients, param_values=pv)
    >>> # print(ar.data_iterator.query)
    >>> # ses.show(ar, column_names="name_column")

All clients who have at least one note dated 2013-07-25 or later:

>>> pv = dict(start_date=i2d(20130725), observed_event=ClientEvents.note)
>>> pv.update(client_state=None)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================
 Name
-------------------------
 <NAME> (116)
 <NAME> (126)
=========================
<BLANKLINE>


Deleting clients
================

>>> d = get_json_dict('robin', "pcsw/Clients/167", an='delete_selected', sr=167)
>>> print(d['message'])
You are about to delete 1 Client
(ÕUNAPUU Õie (167))
as well as all related volatile records (2 Coachings, 1 Language knowledge, 5 Properties, 2 Phonetic words, 3 ESF Summaries, 1 Address). Are you sure?

>>> d = get_json_dict('rolf', "pcsw/Clients/167", an='delete_selected', sr=167)
>>> print(d['message'])
1 Klient werden gelöscht
(ÕUNAPUU Õie (167))
sowie alle verknüpften unbeständigen Daten (2 Begleitungen, 1 Sprachkenntnis, 5 Eigenschaften, 2 Phonetische Wörter, 3 ESF Summaries, 1 Adresse). Sind Sie sicher?


>>> d = get_json_dict('robin', "pcsw/Clients/118", an='delete_selected', sr=118)<commit_msg>fix wrong nb of related objects in msg when deleting an object<commit_after>.. doctest docs/specs/clients.rst

.. _welfare.clients.parameters:
.. _welfare.specs.clients:

=================
Filtering clients
=================

This document describes and tests some ways of filtering clients.

Most code is in :mod:`lino_welfare.modlib.pcsw` plugin.


.. contents::
   :depth: 2
   :local:

.. include:: /../docs/shared/include/tested.rst

>>> from lino import startup
>>> startup('lino_welfare.projects.gerd.settings.doctests')
>>> from lino.api.doctest import *

>>> ClientEvents = pcsw.ClientEvents
>>> ses = rt.login("hubert")


Default lists of coached clients
================================

>>> ses.show(pcsw.CoachedClients, column_names="name_column")
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=============================
 Name
-----------------------------
 <NAME> (116)
 <NAME> (177)
 <NAME> (118)
 <NAME> (124)
 <NAME> (179)
 <NAME> (128)
 EMONTS-GAST Erna (152)
 <NAME> (129)
 <NAME> (127)
 <NAME> (132)
 <NAME> (133)
 <NAME> (137)
 <NAME> (181)
 <NAME> (139)
 <NAME> (141)
 <NAME> (178)
 <NAME> (142)
 <NAME> (144)
 <NAME> (146)
 <NAME> (147)
 <NAME> (153)
 RADERMACHER Christian (155)
 <NAME> (157)
 <NAME> (159)
 <NAME> (161)
 <NAME> (173)
 <NAME> (165)
 VAN <NAME> (166)
 ÖSTGES Otto (168)
=============================
<BLANKLINE>

>>> ses.show(integ.Clients, column_names="name_column")
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
==========================
 Name
--------------------------
 <NAME> (177)
 <NAME> (118)
 <NAME> (128)
 <NAME> (152)
 <NAME> (127)
 <NAME> (133)
 <NAME> (137)
 <NAME> (181)
 <NAME> (139)
 <NAME> (142)
 <NAME> (146)
 <NAME> (147)
 <NAME> (157)
 <NAME> (161)
 <NAME> (173)
 <NAME> (166)
 ÖSTGES Otto (168)
==========================
<BLANKLINE>



Filtering clients about their coachings
=======================================

>>> translation.activate('en')

The demo database contains 13 clients whe meet the following
conditions:

- the client_state is "Coached"
- client has more than 2 coachings
- at least one of these coachings has been ended

>>> from django.db.models import Count
>>> qs = pcsw.Client.objects.filter(client_state=pcsw.ClientStates.coached)
>>> qs = qs.annotate(coachings_count=Count('coachings_by_client'))
>>> qs = qs.filter(coachings_count__gt=2)
>>> qs = qs.filter(coachings_by_client__end_date__isnull=False)
>>> qs = qs.order_by('id')
>>> qs.count()
13
>>> [obj.pk for obj in qs]
[116, 127, 129, 133, 139, 144, 147, 155, 159, 166, 173, 179, 181]

Let's look at some of these clients more in detail.

>>> def show_coachings(pk):
...     obj = pcsw.Client.objects.get(pk=pk)
...     ses.show('coachings.CoachingsByClient', master_instance=obj, column_names="start_date end_date user primary", header_level=4)

>>> show_coachings(179)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of DUBOIS Robin (179) (Is active)
===========================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   Yes
 08/03/2013     04/10/2013   <NAME>   No
 04/10/2013                  <NAME>    No
============== ============ ================= =========
<BLANKLINE>

We can see that Robin is currently coached by Alica and Mélanie, and
that Hubert stopped coaching him on 04.10.2013:


Same view for some other clients:

>>> show_coachings(116)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of AUSDEMWALD Alfons (116) (Is active)
================================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   No
 13/03/2012     08/03/2013   <NAME>   No
 08/03/2013     24/10/2013   <NAME>    No
 24/10/2013                  <NAME>   Yes
============== ============ ================= =========
<BLANKLINE>


>>> show_coachings(166)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
Coachings of VAN VEEN Vincent (166) (Is active)
===============================================
============== ============ ================= =========
 Coached from   until        Coach             Primary
-------------- ------------ ----------------- ---------
 03/03/2012                  <NAME>   Yes
 08/03/2013     04/10/2013   <NAME>    No
 04/10/2013                  <NAME>   No
============== ============ ================= =========
<BLANKLINE>


>>> print(dd.fds(dd.today()))
22/05/2014

When Mélanie opens her :menuselection:`Integration --> Clients` list,
then she sees the following clients:

>>> ses = rt.login('melanie')
>>> ses.show(integ.Clients, column_names="name_column", header_level=4)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
Integration Clients (Coached, Coached by <NAME>)
========================================================
=============================
 Name
-----------------------------
 <NAME> (177)
 <NAME> (118)
 <NAME> (124)
 <NAME> (179)
 <NAME> (128)
 <NAME> (129)
 <NAME> (132)
 <NAME> (137)
 <NAME> (141)
 <NAME> (178)
 LAZARUS Line (144)
 <NAME> (146)
 <NAME> (147)
 <NAME> (153)
 <NAME> (155)
 <NAME> (157)
 <NAME> (159)
 <NAME> (161)
 ÖSTGES Otto (168)
=============================
<BLANKLINE>

Robin is there, but Alfons and Vincent aren't.

Here is a list of Mélanies clients on 2013-10-01.  Mélanie can get
this by manually filling that date into the
:attr:`lino_welfare.modlib.pcsw.Clients.end_date` parameter field.

>>> pv = dict(end_date=i2d(20131001))
>>> ses.show(integ.Clients, column_names="name_column", param_values=pv, header_level=4)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
Integration Clients (Coached, Coached by <NAME>)
========================================================
=========================
 Name
-------------------------
 <NAME> (116)
 <NAME> (129)
 <NAME> (127)
 <NAME> (133)
 LAZARUS Line (144)
 <NAME> (159)
 <NAME> (166)
=========================
<BLANKLINE>


Note that

- Robin is *not* included since Mélanie started coaching him only
  later.
- Vincent *is* now included since Mélanie was coaching him then.



Filtering clients about their notes
===================================


>>> ses = rt.login('robin')

Coached clients who have at least one note:

>>> pv = dict(observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
============================
 Name
----------------------------
 <NAME> (116)
 <NAME> (118)
 <NAME> (124)
============================
<BLANKLINE>

All clients who have at least one note:

>>> pv = dict(client_state=None, observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================================
 Name
-----------------------------------------
 <NAME> (116)
 <NAME> (117)
 <NAME> (118)
 <NAME> (122)
 <NAME> (121)
 <NAME> (124)
 <NAME> (123)
 <NAME> (125)
 <NAME> (126)
=========================================
<BLANKLINE>


Coached clients who have at least one note dated 2013-07-25 or later:

>>> pv = dict(start_date=i2d(20130725), observed_event=ClientEvents.note)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================
 Name
-------------------------
 <NAME> (116)
=========================
<BLANKLINE>

.. show the SQL when debugging:
    >>> # ar = ses.spawn(pcsw.CoachedClients, param_values=pv)
    >>> # print(ar.data_iterator.query)
    >>> # ses.show(ar, column_names="name_column")

All clients who have at least one note dated 2013-07-25 or later:

>>> pv = dict(start_date=i2d(20130725), observed_event=ClientEvents.note)
>>> pv.update(client_state=None)
>>> ses.show(pcsw.CoachedClients, column_names="name_column", param_values=pv)
... #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -REPORT_UDIFF
=========================
 Name
-------------------------
 <NAME> (116)
 <NAME> (126)
=========================
<BLANKLINE>


Deleting clients
================

>>> d = get_json_dict('robin', "pcsw/Clients/167", an='delete_selected', sr=167)
>>> print(d['message'])
You are about to delete 1 Client
(ÕUNAPUU Õie (167))
as well as all related volatile records (2 Coachings, 1 Language knowledge, 5 Properties, 2 Phonetic words, 3 ESF Summaries, 1 Address). Are you sure?

>>> d = get_json_dict('rolf', "pcsw/Clients/167", an='delete_selected', sr=167)
>>> print(d['message'])
Sie wollen 1 Klient löschen
(ÕUNAPUU Õie (167))
sowie alle verknüpften unbeständigen Daten (2 Begleitungen, 1 Sprachkenntnis, 5 Eigenschaften, 2 Phonetische Wörter, 3 ESF Summaries, 1 Adresse). Sind Sie sicher?


>>> d = get_json_dict('robin', "pcsw/Clients/118", an='delete_selected', sr=118)
{'fraction_non_alphanumeric': 0.293459694749022, 'fraction_numerical': 0.06005840542178632, 'mean_word_length': 3.76878612716763, 'pattern_counts': {'":': 0, '<': 205, '<?xml version=': 0, '>': 465, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '317278', 'n_tokens_mistral': 6872, 'n_tokens_neox': 5913, 'n_words': 1500}