server/src/tests/Controllers/ThemeControllerTest.php
author ymh <ymh.work@gmail.com>
Thu, 20 Oct 2016 17:27:36 +0200
changeset 377 52169c718513
parent 322 084aae09edf4
child 387 7fba86fa8604
permissions -rw-r--r--
make all stats view use elasticsearch. Make sure that the document discourse types are taken form the predefined list

<?php

use Mockery as m;

use EasyRdf\Resource;
use EasyRdf\Literal;

/**
 *
 */
class ThemeControllerTest extends TestCase {

    public function setUp() {

        parent::setup();

    }

    public function tearDown() {
        m::close();
        parent::tearDown();
    }

    public function testIndex() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => config('corpusparole.theme_default_limit'),
                                    "order" => [ '_count' => 'desc' ],
                                    "include" => ".*\|bnf\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "professions|bnf|ark:/12148/cb13318415c",
                                "doc_count" => 1412
                            ], [
                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
                                "doc_count" => 1092
                            ], [
                                "key" => "famille|bnf|ark:/12148/cb119339867",
                                "doc_count" => 1050
                            ], [
                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
                                "doc_count" => 1003
                            ], [
                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
                                "doc_count" => 1001
                            ], [
                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
                                "doc_count" => 995
                            ], [
                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
                                "doc_count" => 989
                            ], [
                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                "doc_count" => 989
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://ark.bnf.fr/ark:/12148/cb13318415c" => ['label' => 'professions', 'count' => 1412 ],
            "http://ark.bnf.fr/ark:/12148/cb16604691s" => ['label' => 'travail non rémunéré', 'count' => 1092 ],
            "http://ark.bnf.fr/ark:/12148/cb119339867" => ['label' => 'famille', 'count' => 1050 ],
            "http://ark.bnf.fr/ark:/12148/cb11932889r" => ['label' => 'oiseaux', 'count' => 1003 ],
            "http://ark.bnf.fr/ark:/12148/cb11932496x" => ['label' => 'météorologie', 'count' => 1001 ],
            "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ],
            "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
            "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
        ]]);

    }

    public function testIndexAll() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => config('corpusparole.theme_default_limit'),
                                    "order" => [ '_count' => 'desc' ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "professions|bnf|ark:/12148/cb13318415c",
                                "doc_count" => 1412
                            ], [
                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
                                "doc_count" => 1092
                            ], [
                                "key" => "famille|bnf|ark:/12148/cb119339867",
                                "doc_count" => 1050
                            ], [
                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
                                "doc_count" => 1003
                            ], [
                                "key" => "Français|lxv|fra",
                                "doc_count" => 1002
                            ], [
                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
                                "doc_count" => 1001
                            ], [
                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
                                "doc_count" => 995
                            ], [
                                "key" => "Breton|lxv|bzh",
                                "doc_count" => 992
                            ], [
                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
                                "doc_count" => 989
                            ], [
                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                "doc_count" => 989
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/?filter=all')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://ark.bnf.fr/ark:/12148/cb13318415c" => ['label' => 'professions', 'count' => 1412 ],
            "http://ark.bnf.fr/ark:/12148/cb16604691s" => ['label' => 'travail non rémunéré', 'count' => 1092 ],
            "http://ark.bnf.fr/ark:/12148/cb119339867" => ['label' => 'famille', 'count' => 1050 ],
            "http://ark.bnf.fr/ark:/12148/cb11932889r" => ['label' => 'oiseaux', 'count' => 1003 ],
            "http://lexvo.org/id/iso639-3/fra" => ['label' => 'Français', 'count' => 1002 ],
            "http://ark.bnf.fr/ark:/12148/cb11932496x" => ['label' => 'météorologie', 'count' => 1001 ],
            "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ],
            "http://lexvo.org/id/iso639-3/bzh" => ['label' => 'Breton', 'count' => 992 ],
            "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
            "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
        ]]);

    }

    public function testIndexLexvo() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => config('corpusparole.theme_default_limit'),
                                    "order" => [ '_count' => 'desc' ],
                                    "include" => ".*\|lxv\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "Français|lxv|fra",
                                "doc_count" => 1002
                            ], [
                                "key" => "Breton|lxv|bzh",
                                "doc_count" => 992
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/?filter=lexvo')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://lexvo.org/id/iso639-3/fra" => ['label' => 'Français', 'count' => 1002 ],
            "http://lexvo.org/id/iso639-3/bzh" => ['label' => 'Breton', 'count' => 992 ]
        ]]);

    }

    private function getOrderQuery($order) {
        return [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => config('corpusparole.theme_default_limit'),
                                    "order" => $order,
                                    "include" => ".*\|bnf\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

    }

    private function getRespSort() {
        return [
            "took" => 27,
            "timed_out" => false,
            "_shards" => [
                "total" => 1,
                "successful" => 1,
                "failed" => 0
            ],
            "hits" => [
                "total" => 3011,
                "max_score" => 0.0,
                "hits" => [ ]
            ],
            "aggregations" => [
                "subjects" => [
                    "doc_count" => 41524,
                    "subjects" => [
                        "doc_count_error_upper_bound" => 0,
                        "sum_other_doc_count" => 13678,
                        "buckets" => [  ]
                    ]
                ]
            ]
        ];
    }

    public function testIndexSort() {

        Es::shouldReceive('search')
            ->once()
            ->with($this->getOrderQuery(['_count' => 'desc']))
            ->andReturn($this->getRespSort());
        $this->get('/api/v1/stats/themes/')->assertTrue($this->response->isOk(), $this->response->content());

    }

    public function testIndexSortReverseCount() {
        Es::shouldReceive('search')
            ->once()
            ->with($this->getOrderQuery(['_count' => 'asc']))
            ->andReturn($this->getRespSort());
        $this->get('/api/v1/stats/themes/?sort=-count')->assertTrue($this->response->isOk(), $this->response->content());
    }

    public function testIndexSortLabel() {
        Es::shouldReceive('search')
            ->once()
            ->with($this->getOrderQuery(['_term' => 'asc']))
            ->andReturn($this->getRespSort());
        $this->get('/api/v1/stats/themes/?sort=label')->assertTrue($this->response->isOk(), $this->response->content());
    }

    public function testIndexSortAlphabetical() {
        Es::shouldReceive('search')
            ->once()
            ->with($this->getOrderQuery(['_term' => 'asc']))
            ->andReturn($this->getRespSort());
        $this->get('/api/v1/stats/themes/?sort=alphabetical')->assertTrue($this->response->isOk(), $this->response->content());
    }

    public function testIndexSortLabelReverse() {
        Es::shouldReceive('search')
            ->once()
            ->with($this->getOrderQuery(['_term' => 'desc']))
            ->andReturn($this->getRespSort());
        $this->get('/api/v1/stats/themes/?sort=-label')->assertTrue($this->response->isOk(), $this->response->content());
    }


    public function testIndexLimit() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => 3,
                                    "order" => [ '_count' => 'desc' ],
                                    "include" => ".*\|bnf\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        // This is not normally what should be received, but we test that the limit is respected
        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "professions|bnf|ark:/12148/cb13318415c",
                                "doc_count" => 1412
                            ], [
                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
                                "doc_count" => 1092
                            ], [
                                "key" => "famille|bnf|ark:/12148/cb119339867",
                                "doc_count" => 1050
                            ], [
                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
                                "doc_count" => 1003
                            ], [
                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
                                "doc_count" => 1001
                            ], [
                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
                                "doc_count" => 995
                            ], [
                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
                                "doc_count" => 989
                            ], [
                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                "doc_count" => 989
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/?limit=3')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://ark.bnf.fr/ark:/12148/cb13318415c" => ['label' => 'professions', 'count' => 1412 ],
            "http://ark.bnf.fr/ark:/12148/cb16604691s" => ['label' => 'travail non rémunéré', 'count' => 1092 ],
            "http://ark.bnf.fr/ark:/12148/cb119339867" => ['label' => 'famille', 'count' => 1050 ]
        ]]);

    }

    public function testIndexIndex() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => 6,
                                    "order" => [ '_count' => 'desc' ],
                                    "include" => ".*\|bnf\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        // This is not normally what should be received, but we test that the limit is respected
        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "professions|bnf|ark:/12148/cb13318415c",
                                "doc_count" => 1412
                            ], [
                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
                                "doc_count" => 1092
                            ], [
                                "key" => "famille|bnf|ark:/12148/cb119339867",
                                "doc_count" => 1050
                            ], [
                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
                                "doc_count" => 1003
                            ], [
                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
                                "doc_count" => 1001
                            ], [
                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
                                "doc_count" => 995
                            ], [
                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
                                "doc_count" => 989
                            ], [
                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                "doc_count" => 989
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/?limit=3&index=1')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://ark.bnf.fr/ark:/12148/cb11932889r" => ['label' => 'oiseaux', 'count' => 1003 ],
            "http://ark.bnf.fr/ark:/12148/cb11932496x" => ['label' => 'météorologie', 'count' => 1001 ],
            "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ]
        ]]);

    }

    public function testIndexLimitIndex2() {

        $query = [
            'index' => env('ELASTICSEARCH_INDEX'),
            'body' => [
                'size' => 0,
                'aggs' => [
                    "subjects" => [
                        "nested" => [ "path" => "subject" ],
                        "aggs" => [
                            "subjects" => [
                                "terms" => [
                                    "field" => "subject.label_code",
                                    "size" => 9,
                                    "order" => [ '_count' => 'desc' ],
                                    "include" => ".*\|bnf\|.*"
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];

        // This is not normally what should be received, but we test that the limit is respected
        Es::shouldReceive('search')
            ->once()
            ->with($query)
            ->andReturn([
                "took" => 27,
                "timed_out" => false,
                "_shards" => [
                    "total" => 1,
                    "successful" => 1,
                    "failed" => 0
                ],
                "hits" => [
                    "total" => 3011,
                    "max_score" => 0.0,
                    "hits" => [ ]
                ],
                "aggregations" => [
                    "subjects" => [
                        "doc_count" => 41524,
                        "subjects" => [
                            "doc_count_error_upper_bound" => 0,
                            "sum_other_doc_count" => 13678,
                            "buckets" => [ [
                                "key" => "professions|bnf|ark:/12148/cb13318415c",
                                "doc_count" => 1412
                            ], [
                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
                                "doc_count" => 1092
                            ], [
                                "key" => "famille|bnf|ark:/12148/cb119339867",
                                "doc_count" => 1050
                            ], [
                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
                                "doc_count" => 1003
                            ], [
                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
                                "doc_count" => 1001
                            ], [
                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
                                "doc_count" => 995
                            ], [
                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
                                "doc_count" => 989
                            ], [
                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                "doc_count" => 989
                            ] ]
                        ]
                    ]
                ]
            ]);
        $this->get('/api/v1/stats/themes/?limit=3&index=2')->assertTrue($this->response->isOk(), $this->response->content());
        $this->seeJsonEquals(["themes" => [
            "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
            "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
        ]]);
    }

}
{>{KC/%G4FK+-KF.Ԅ/Bc~$MFpM 5%'7a]"?r Z0S1W}ĝ.4uW48+QB598|ͦF!rXL\1ᵫ|bYHFKZY-+M,H#0K"2EX1w,ZZFy,r/~ຖ#E)bm 2EBs!u=<{Uy6XZXZI̢K%7Nu/S1tݏM/w˘x~L/bb\,Xbŋprh-wG0<`d`TQ|wW L<ʪ}l;}+=mR<5G[}%_z}7L-WlBY 1> Ǹ~c͎ܵ;k 8C$DEmaр(k8kHt='6دʈ*)!s税C]%\MScTⵏgU:I5MK,ܤê\Et8u*4 ,-'WY1 Syd,%uU¾+^r}7' m'_g|r7"i/gc"(k Y4V-"%E0I&pYXJִ[dx~ ,BR#Qv=٦j4j^1SՔ0`)WՅb1V‰jeR|8<󃍿845OCəfݖB` bB *vνqƝz&IĞ Si9Nw44qNH[w}@:B PBOM; NiNi❩ڝSUI+Nb]G$۬sI맍땙}R{\粢y?o6ȲZM,,K֝bcc5PmVZ+Xbŋp/Xi}z[ T.{8b.Geϒ}ߖg$`8]4ҩ`f'T~ `}wlpMD`6 $a*"[׫LM.q8y?1G1?n6*E K6GH2>b؅ee-6r{/YwB#veV".\r| ,%/\$hBNdcrXpM^.#@?1AY&SYe" @ `|w}%շ7Ѵd[b[zs4^n'/|u"V<ZAn|+-h4P*tFQ'sos)@ (mm;uZq@*DBp@W/ 03R`vtO&NP4u hܵjS6٥((4l(,ƴZlZ*(`:U璻Q퀢Rtx=V0-t+@{X5Er-iiLTCG 4 B(yFOhha "dɥ3OT=OII47zLM @0$DhLCMSToTJiSOԌ=@ DƍhMR"fhЧ0F$&4 bzMOe12yCFM P4d?7c+t: M+Cc^|^4@5+XbJ]2jՅ1 W@ `@ %XI> 4b*ֆ_4??~6쯋3'8qQ$I}es(4F.&-+&pS.KHx%sO mkSH `97 IbߝOjLq?eZDBlP*pf( V[e_>2S@̍I IFm-:m,b)w ʈ[t7f޵h IO hr%"`txfyU6#oG b-ML0W|wT!6X+UE3WP2 H5)H*IzJ/^$ 3<1CV B66sۘ>]dگ QITQMq޴QgIwQ'GV|p b^X !$oݾu,U݉>jVT>O-wv`+̀~6opML%Y/`wKK{q E~~i+}yg`;vzB¢@DOjustۧĉ֑$Bv/<*K'~^kaU%QUR^Fkd϶κk1IEJ~AJ yNw?KC;8\fxsWedK _g^b@pADN + !y!Niux#~Tt@xƯzIc g~07;` TQۈYRwpcĆu~exgˊ뭙p$@e e3 *q=V_]rJZ%* 'yE џHc#\A{sO1Hw]7 FV?M5b`Ibg 6Q4%|@\*g#dajLeVʱ='qgcHh8epv<=yh"o=i{&QՇP<_(~vQ̄/~{P,m8RDL ZODH!A*Z*9v/OԞ2xHa~?; 7ܡt]|x/+ȍ\"oc}ˎRa~/Yi`줠7tdtTvGخs^E1k5Y됂T)%*ܸI]%'tLT-l/1Ɂ4i{Q#GQSWg~3 1}}.kc7Ҙ`݁vht^Kby;$U,cJipҘV} 3ebWW'&̋q\[`q/d*J̭Cm QIW}PgA4q /.Y&̒ #w@LK#%`@ |+Q$3e: _,lYT=cR`h44Di֞FઘtM'Lpޢ(*S|@v)4kt]Óf`xRlFBHv5Zs ML$U.(Qa72$b|3'< GOxɽ¦ ^(v+3!;8a[8ZmQםbZ:( r#骠vѱv<]fתK6PQ{ 1Tכ"EY;"䉝dΐu]%P鍙bX Jsn(Uj 3|$uʍ_I ;Pa+ZO5uN%Tf r.xK[;1W>3˪i]9+|" )Á E9w9}}^ sqobd,,zѣl^ rָ4]h}*$ Gx^@ʸ1@[wl gK,w /D}[b0oXExWc9M0~pQYﻗ y*Y]2 @t,ftUcYVDD\ןCAR>=%:y F PʁDJFйMCVPJo{ =ܘ:v7IeDLd0Rp̏`9@ņՀS0K9ʞ KV?ơIʤv;j|`ud`5+Fl;$PT1M*EU YfY a T˭y-ruP9"'AznQMf9{ٷ\˨\H7FB4` \(0\̽R@ K}}Vap@]LNJ 03 .tqሥ3.s`70l?yz}Hf #Mpq/ <^So#ZרuH9iv8ـ o"f;寕-֛(`x5;&r>B,D2N1* alt'9X̰edy9$.K$9X?Lan]+2]YFmQ[& q,voj";ht娶ݝ(4aF,ȉ 'vNaA-5Yqw=O CҊՀ RjEV%yH .2UNK& BmGEkeMгMۆ4c}VC*rۉӲ\L9\}BU#soisf{r(6 p=tF1%V$v.ݶuڬ3v뒈ļťD ɇ_@)Z߿-J1WHe4~DR0 UܴrWvN+͆GnlN6g=4}yk+=#rŠV0'&#L[^-LuA"d1zӏ:g%y<,fn/VʡN-t37+V{w踄@NibQqSJ9 dk3~H X;;m '3|sX( H>-SelfXJ̧ [4s7 UF/jnHFCGC9$E-~9y1\O1#{S ~ʇH#kxӯhq6 3!}~.0&2/]d~句nyo$ $-e V.ˆ d8(@Y:TF Z}崈M@=`( LQ^PR+(C dL .Ǐ=yn_j.^c}gz|PL ybH];oϋ^V{RMuΜyy$zV3AxSD-o=<( EsֲmHEK}UjBZ"X@.o` Td&Ž-v~#g?46> 8u*Yu~O~-E6#x^Nd--Dϗ:_q#_v+fs'GȌlANX1#,! DVa!44mhm4e$Q䥊5S/| 4Б- SfqS'?I ֹ'H9Gf>x%N#~49W>M "2*CݵRB͝l47 !Pgknt>(2Z?&@:Z 4q{+q| }f'>  D?Ho0k>ڏ2CU~ }CO0}qҎLq%G1Gq7c>u997exǬz^dzGq8!w=GG#teڣ?nDu*8dJCQd`G:H1FC;EĒI}{FC2222HFFGXx41;440`d=FQG20n8:C0棁|#a;C;QhU9J}#ʎ.67Ǎʺ (dKyc2F\Qt:֎aߨۊKG[CC;"IjPFk5jhG|ǘ2229cȗyCpt Q< ;Czdb,Q{Cp; <CjF ~hn%p<ǐQr7  0`FF|9Z7G-ѹWzQyCpx  4t߾ʱ0Np@L??w|"('دG"V+*{A|H+eK21GId8Cpl2lqq20d`ݔ.T~whtCP⌇2YQs FI.j:єnk(-8G](G7Tn%n(#TUG((GNOY+ZOpk}TarW \%sV- k͂P2E6{;3|5ě23哰دbWl+حC8Ee Chr2##`Ch}cC0w#cQ?cr8zG#H<442 #c;a: #!6=67c:G9:F#GxG=5GH2#Qs\|QdGc>qԗFFTyIdj:G|vXQjId{QGx8FFF:GH:G##tU(F5(Gj7Q151q1Ѩ#GuFGhcCCpq>Yhs;ܮrWvYSnYܮVhv`2oiVQ>2+XjQ3k+J WLeS=+tPR8+~"خoD֭ͷ[w%q?"SDLNr4?Vq8K͘>ֺC>{z̨:';Gosu$ݹ=?rtYs| h$#=68HxtoX2c-\9t]T,8 'e"_:΁D( |+UPC7xT Y>bZ:87ޠsN Ub ϛ˖ڭq_-4glݴ2B"oF3~{ IP",UEFnt#M16mAf`DSU$Є9YaQ-)au4FI5)\3@afY`DFAfbdFaf88B(P@X6 P M Xaea=΃Cơ8nC!C"C0r0}C=a0Ƈ`;QGxyÑ =FǀQ4?( CcCGA;p;h`0{ CAl{1u#vH;ǔs1=c#22=#z8Q#q7DjH87nѸ{wƣ?${G}1?cj##q#tvQ#qƣ7cQ:Gj<5Q(;XqGd}h<ǀ@0yh|G0hvP6Aw苰|FǨ xF(h{|1;aGcp9FƇh`5Ca8Xp?pxad4=ì=u ld7Qy Chx vp Ez"CXb-CPxqG:ֆ)r1IA*R4; (wa`l`pG$2 2.T%evWd_ERL@Y )2c9"ÑJҔ aYD%`AR C |ơ9 PiA6>XF?a$3AļG? ?li4Jx PG0ơp *%.!/a`8r0n*RʇjJ]ǀڕRFYC1XI^xx)H ajhBdO9SQ&I BQCJQHLD'Y#/®Gk 샧At1h}2? 0`421X\VF>neejk^KEld+i_uXn7Qvll` 0` 7FA1qGqGqGZ?45FǍ+w!xulyGsGX8Cp4<0<#:Q< 4v:֏[w#qQ/m??<x FCcc 7G 0` 0` 0`֏lj4650qGl8n7  - 0`42<q4>_sFIzQ=c##ہF Ahh~o˪? 0` \Q<; Qo: 0` 0`J440` 0` 0` 0` 0`y.Fr8W+7 }Q~Z7 0` Uʍ QV=:luc=]p~Hhj5Ga}=r5GGƆ>9L(#F٘x?>g[chd0`w}#G-F##v=1r?n t;]Ot`P<25QtFFFG}q242#oZ?7G45O0C=ƣ*_zm$p]0^>=e|GxZ¸G0ݬjȇ3>$!- `q2-V\#q'8Ơ{:*r#ڣ{E㡤6Exjj}\(YQ9VQ]G;GHj<(d$lhnFޣu(x?U~,FTk),Q>q#({FGqŔ~}~\Qڏ}1j1}o +_ywUV$n~_[s,Zn*ި8*>9|27G#M####|/sZF1WHҪa{K/Kw<~̿`-Wn/? {'{UX?gU)Hj22222222>qGKj:1G. 0{"1`[b/X,=zZ 0<f |ddddddddddddddddddv򳞷_ðln7qGxڨØU>{8,,,,,,,,,,,-q7E o&KATSh᰼_sj5CQFFFFFFFFFFFFFFFFG<.#W/k)/3tA`X, aqރ0}Z+~h9G FCc|cr9cj;}{csFQ@?Y `vrňŘz&7l{d7I9/WXg˄-yLe8 xۚNh$} \oUPl.c=Ÿ xFHwgIPQ]"|x|?\_&/'=r"{K}J>ϬoxQd>cQn"oz~NL|H|]ߏ aw>h!F9L !euF@CaC{o|"'_)c ~k.Lݒ[Q D+aMT:PR9+K?\su\O,%5NG<&ă0.I5R]c#$pS(n2*װ:P2*:JQq)dO΍Q|j`/9j2QY) FQ5FFQccPj51Fơ\P|~5Sv'BA0_Kq( @)"/Ғ,ȴGwh/#οP! Y2F[3?%C߬5d2W{_7uy#3I Eέ(>GGA*WTyFQ+VY~/wE^!^J-s·_`Q#IS}*`{+edd{#q+22Fz LtdO$ Dcl^Rg> aUF$_9S4` hW ^]X|[MY[6ܯk5! SYffW'J.xPY~1#~fR#P`5Fq',b ڕW/|Gj44>,QT`;??=F 41)do+Hs|GzFFFFFFFFFFFFFFFFFFG P9XXYYADHB] + +Oj0D/5 ?&B=Bbdd`|ddddnQ*`UZH_b^R3`,, s` Z"*"B&$j)"  a' PB{ zjHvG }W^"AaX=2vdv\.5K####### P-#+ZZ@]¸ %Z6_uY-]TiIvVo\c22225GCFQ####^( bb8u`B5h!̴lIR0h p/y"/q^KsJ YP:$7Ue22):b ֹl:># ;LdCun!*ұ[U/O!wJLE[ ҆ 5UX-. )5"i(XTS쩊{Vj"{GJ|GH.5 WxƏ"U+U3xľ=jQ1F(2#  0uEd]%yHnEѐ2QFu>N >aeepߦ0(((Vb"yp#3(GIw ,Qki^1F|/VnE U HKFq27FTjQAW( P,Ƥ%iY0Q8GV7h`e%_Z'5bA!"v;\{?=ʅQD!nqzެ+l̖J:@W[ngN4Y7*^4(qv^N"6t 0NpXj('q!nuc@jrZCWFMՕsYeўkZsXp#~ tkz(2и+TPskJ5EVFA=fW#r:>W#29ёhRY5Jw(󚢙ɻrWg({w_TVo;1zeZ? hG6vU`kXTP2ՐEj0FQYffbH ]" \dX9Qr4Tefi0`stdb, ]@"4$%" # $$$ib$V## 0$(*`("0̓U%v># =#nsKW-E^Ӎ}(e/*2#F 0y}1d`=(8V1ezb0` :G>)یD{*UzTws9^ H*[{PϢϢ_8zK4O(YT kDPPĬ*b2)Jhld0a ׎R//-#G監!# BXZ*9/wTp#mFc*5F5Ud88%2X$gzGGd`δ;wEWޞ!δSK#R+c##R,(VW`SP9:1ɽ2 @ _d=*+`"CO˘BĖW-k/yyT^BiC T|?20 o:2O/ҟHuT!(, YFPF FF 22s޿ws) ~4WtZwG뢭C*0j2FF?ί֮ PMf|@W55 Ţ!*zV|盩\@+ Y#Cp8b!܏|WD_P'YXW!@$dF##$1rʯj26:+#w.>'2@Dv &>HsR2D5K"FiT3P'o Y:CqTj8bڑrqyt~'uP7 ]D'W~r>xo|[<x}UW⾺(: 0` 5 K2T"J"̑!{sGiI DJT. M!V4``B3*a{mLiŠ PxWGyGN~|,-(%V#"R[j8 Q4x>q|/B'0dim~CE^Pz#D8L! uKb_Ue#"UW0 ]lIj-{Պ];XEq?PE`0eF_'kU*- P- ?{?ThdhtuZJӥqD9/]C NJ 0+2# QbʱgZ6Ut[ _<5(y,ˋ E܊m57xwlӓF[:=z$7Mr}Yt ,ߝ]dՓ9k8a@8*tUDkM ΩH {1tGXuvP7*c:sG2ZёzGXF#QuƑu+yuʷj׍뎛l`ej7FFQ:Gx225QiZt:#!)ۚ2KR>x+:"҉IS˃C'd$O!{T!%KQî?gb1=8~ܭ|C!}22##(4( =W󉩬ח GXg2o{I%6:k@В*Sw[T9#285F$8;Cc? U22;C#QdqTj2FIwe/ 7Guhd6Uj:[6Ҽ">Ҙ220ȌQ#@aqS<`v:0|}n52VT%d\J6GJ_R4:O'k_% G+eGU~LŠsG2wHhb,2H40i !l|(+e 4%| ѝ5|7FܕF(v=0J ̕فEC1bnvģvԅi,Rm5 f츙[ jRt]Wxw촾2- Iy#CcBrvr!(XmiCjzgo W.F A,HU)DV =LBxyYQTeRY)p p⋀2걽XRR4_U BR>խZUpY|aֺGmF2FȖK(6ZGx8FWbdp}!Xo2dm>G EEo.Q߸^xe: v&b K7B,+( l:huFDDEQdT½"#-}>0}?9 g]ܪij?n.?I|7P LǔK8e,r@$bEQ֡:(g¨xõ}8+h`cEвR)|}>|s{C IG (qv*_yfIj61)&4#C3w dn1pjf(u B!z-L[aHcVTc2;[F+C ѓg! 71c̩ic۬H8cPZ , ecKMCq$J$miQ) ?EVbFa`6|A2WltS @,L"ʭ)j[F#׉RgA`hxʈsۄ)D?0 0` 0`CCCThh` 440}!`:/mplƪIčRajr vrp jj"VJW\.0e`̵nݢӶ¹1A _0۶d`\q02PIM-j9j=E+PѺεZ>EON3JGLsvxH2STkwCymس[o!?H-6<{v~֚Ɨǿe7!ExGLྕ@X*),,A]>#*3Vc%>=G`ʽj=*t̰Q%fQ?jQ ŸYP{¨d0CZRZ7[q20e%R{ _BJP)J@(,@".?IǕv_T}R22G. 0ddݕR<5)6I`FT]hc1Wۓ*7+ƲU鄬e-m1UejUfP1d=L| P>PTC$8 |U36S рQCp7 #(2250{!wV鮃##MC(G8\*pxpq4qjF m ##CTx\VQb1FYFTbuF# 3O)7vmZJ0p%sFugAo<'629e ZZkXlb22?78tu!b22:Q8Q׷7jF- p}V}~oy?vH9UFzMLFF2%$YGu󑾘ѡ`Fv> eWNDW=!;` yxe`Jʧ"hnX20eG񌌍40*hh`hb47w7sG+02EןQb#%azՙGS3me=FC8Uq7n\,n<1\12HB/acQ :O'3QO=Ҙ@ D꼖ƫ0| 0`[[\ 5үT>1ognMS@Pq2C+,b|9 *# LhŌ`2 02tc32Yʋ0-(4.pW\C!VH­ 4B) ⲸFQVFIdeF##ƣT?@ʓCZc HXj2229Pa+c֨Q1dʍZ(KUVCCQ###Tjԍb7IkZs(+~C#!o>Q##F#!}e!9*'.rGOEX^B('w bT^aA J)02s\+UWohZJFh+PW#G?M^p,ӈf ʕwefW~ 0bXeuuU%˸O =$))zX^묀vB/Z]OgKu@؋q\W| 0~#Cq7〭|Vԉ.Vj^P!xeeh|cds"I6D"X+ R~heURJ~?qڲG\%)o"xQk`;UUڑԋJSR6Q0ijV,v]ԆCX"e8aQb2hBJQ^miXR?5+RQ<=22z4 , "W! IB,- ‹@)@'vC1"$b222!dddeFCq5F굋/7,TdQdj i*ʑ%c%lZDN C~9i]6%vLtย$}&Gj1&>uXPq٨4uf: e{bYBuma0^'*<SOl\öC;XlIM>x3,1!d ~Dуn2f=8rIBԠp$@P u (O5 nf$ZybU9ӆ'e"dꛑ@KYRxžx|=Q`x6Uݩ'N U}׹jDw } d.AEƅ Gtcde"22Q- hQ(jKI4&fgMsc,7o/؀Q1*qI%rV·X1<ǀ2K:##U7v~A/= lVQ\.U|ќaE^%txϬeڡiUt^ߓq+4ֳ,|}9Z+1O,220`)\"̑dƌ{`ЊҷW0I$xQFQO}e]or9441\6@P*p/:1Wp444444444<]?F9GRN*$"X""TH E]*E$0lthc ձTR. PFIj25F!hheV.ljQ 44KYKh(6zVU確y/*<6dwчl~zkC&Cr~99yCP-ȭ#~}4dtѱ D:0_\=^C=!gSFCpY=f:`V\E~; UL׶T$:40 ,g VVQo7_#*,WJ2%ljEa,Y ],5Q w}ȋ=?eKT}eXL$!AB6$@2&l@,x@t#l(R% T,, AK_@"ך:VP;Ȧ ڨ>y0`9|b2`?U]DC) GǚϽ7?^})cXث~]ŁR ꏺ`#C*X r .cIm}{qDA,BƭFFj241VRݙ{ʤXK#!"++J?a(`^u]q[aVc/;jƆ$x::^`Xc3 ²1I2,+)UT`,:zH~/ XkA^NQt\m-'ZJb0` 0` 0` 0` (=l5zU /) e+뎄 0!|! (ũZHn#CCX*ɪ2_g5 HҐFk`U}! , xtP7<tB-J+/P.XY>cP_1Amzzodֵo)(hK.3I0l=E8x|)_@UIMpރbm Md뇎כ׍wʝ#(cn51Gwðp0o[*GZ:1n518r0p;è掐C8d+AB!"Njk` \krK_,!J(h=b ZWXJ9y*$Xv!ʦ3PtVZ-+ե` ԮWK*PGۨ]y]]lt+ P%~k?rI=/ݕަ\[{m+Ntxwi߰t>۩ `@!! Ap!eӒٜ~ygnA@MD B .FjYw#LagPAA1!.Ρ!BY%^%(v@6n6! EUfd; n.74xDěީ[u?f "LS7(sE3,Rܽn20hgcVYwN$Y宪q%;bMu|U\/9+tIF$}bL1