<jupyter_start><jupyter_text>Spatial crowdsourcing exampleIn this example, we will work with *field tasks*. Field tasks are available for performers only in Toloka mobile apps. Requesters set the field tasks, and performers choose them as points on the map. Performers are usually requested to visit the place and check something in person or take photos.**Examples:*** monitor prices, products, and outdoor items of interest ([case study](https://toloka.ai/blog/fuel));* play the role of *Secret Shopper*: leave reviews for stores and cafés;* collect data on businesses: whether a particular organization is closed or has changed its office hours;* monitor promotional events;* check on outdoor advertising and many more.>**Note:** For more information about field tasks, check out the Spatial Crowdsourcing page on our [website](https://toloka.ai/usecases/spatialcrowd) and more precisely [Toloka Requester's Guide](https://yandex.com/support/toloka-requester/concepts/walk.html).  IntroductionIn this example, we will collect pictures of the Moscow metro entrances.Because usually spatial crowdsourcing assignments converge longer than others, for this example we have chosen the most frequently used stations. Many potential Toloka performers visit these subways every day.This example also can be reused for production tasks such as monitoring the state of objects, checking the presence of an organization or other physical object.For example to check:- that the area around the store is clean and tidy,- that the store has placed a new ad at the entrance,- how your advertisement was placed on the billboards,- the cleanliness of the waste disposal areas,- that the bench is installed correctly in the right place.  Data preparationIn most cases, field tasks are tied to a specific point on the map. In our case, these are subway entrances. The performer must come to the entrance and complete some actions.There is a way to set these points on the map through [toloka-kit](https://toloka.github.io/toloka-kit/).But first, let's take an example of the Okhotny Ryad metro station, from which it is easy to get to the Red Square and Kremlin. Let's open the maps in [Yandex](https://yandex.com/maps/213/moscow/search/metro%20Okhotny%20Ryad/?ll=37.617426%2C55.756762&sctx=ZAAAAAgBEAAaKAoSCfFmDd5XtSVAEXmxMEROl0hAEhIJAAAAAIAdV0ARe2X%2FuFTeQ0AoCjgAQNytB0gBVc3MzD5qAnJ1cACdAc3MTD2gAQCoAQC9AVmBJ6HCARH6tInp5AKeqLT7A%2Bi%2FyPT6BuoBAPIBAPgBAIICEm1ldHJvIE9raG90bnkgUnlhZIoCAA%3D%3D&sll=37.617426%2C55.756762&sspn=0.007170%2C0.002595&z=17.41) or [Google](https://www.google.com/maps/place/Okhotnyy+ryad/@55.7574454,37.615033,18z/data=!4m5!3m4!1s0x46b54a5adb3b8d5b:0x22be0270b70ed47d!8m2!3d55.757689!4d37.6164801). And find the required entrances.You will see something like this:      <img src="./img/google_map1.png"         alt="Okhotny Ryad on Google Maps"  width="1000">        Figure 1. Okhotny Ryad on Google Maps          <img src="./img/yandex_map1.png"         alt="Okhotny Ryad on Yandex Maps"  width="1000">        Figure 2. Okhotny Ryad on Yandex Maps  Now, to get the point on the map: 1. Find the 1st entrance to the Okhotny Ryad metro station - a red-letter 'M' with a number 1 on the left side of the screen.2. *On Google Maps:* right-click on the entrance point and coordinates will immediately appear in a pop-up menu.    *On Yandex Maps:* click next to the entrance point, then click on the pop-up card 'Tverskaya Street,1' and coordinates will appear in the left menu.>**Note:** It is also helpful to switch maps to satellite mode or check yourself by panorama views.      <img src="./img/google_map2.png"         alt="Coordinates on Google Maps"  width="1000">        Figure 3. Coordinates on Google Maps          <img src="./img/yandex_map2.png"         alt="Coordinates on Yandex Maps"  width="1000">        Figure 4. Coordinates on Yandex Maps  We will get the following coordinates: 55.756916, 37.614546. These are latitude and longitude geographic coordinates expressed as [decimal fractions of a degree](https://en.wikipedia.org/wiki/Decimal_degrees).  Create a project Set up the environmentSpecifically, we will use the following libraries:* `toloka-kit` to develop main Toloka functionalities;* `pandas` and `numpy` to perform data manipulation;* `ipyplot` to deal with images in Jupyter Notebooks.<jupyter_code>!pip install toloka-kit==0.1.6
!pip install pandas
!pip install ipyplot

import datetime
import time

import pandas
import ipyplot

import toloka.client as toloka
import toloka.client.project.template_builder as tb<jupyter_output><empty_output><jupyter_text>Create a `TolokaClient` PRODUCTION or SANDBOX instance. All API calls will pass through it.Obtain your [OAuth token](https://yandex.com/dev/toloka/doc/concepts/access.htmlaccess__token) from Toloka or [Toloka Sandbox](https://yandex.com/support/toloka-requester/concepts/sandbox.html).<jupyter_code>toloka_client = toloka.TolokaClient(input('Enter your token:'), 'PRODUCTION')  # Or switch to SANDBOX
print(toloka_client.get_requester())<jupyter_output><empty_output><jupyter_text>Create a datasetWe will use a ready-made dataset of Moscow metro entrances and select several stations from the circular (koltsevaya) line.This dataset is compiled by the Toloka team and is distributed under the Creative Commons Attribution 4.0 international license[![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/)<jupyter_code>!curl https://tlk.s3.yandex.net/dataset/moscow_metro_entrance_2020_en.tsv --output dataset.tsv
dataset = pandas.read_csv('dataset.tsv', sep='\t')
dataset = dataset[dataset['line'].isin(['Koltsevaya line'])].sample(frac=1).iloc[:5]
print(dataset)<jupyter_output>% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  108k  100  108k    0     0   499k      0 --:--:-- --:--:-- --:--:--  499k
     Unnamed: 0                                            name  longitude  \
656         656       Belorusskaya, entrance 3 in eastern lobby  37.586130   
158         158             Komsomolskaya, entrance 1 out lobby  37.655917   
762         762  Paveletskaya, entrance 2 out lobby Circle line  37.635877   
120         120           Krasnopresnenskaya, entrance in lobby  37.576931   
657         657              Komsomolskaya, entrance 7 in lobby  37.656598   

      latitude             station             line  
656  55.777624        Belorusskaya  Koltsevaya line  
158  55.776730       Komsomolskaya  Koltsevaya line  
762  55.731687        Paveletskaya  Koltsevaya line  
120  55.760413  Krasnopresnenskaya  Koltsevaya line  
657  55.775091       Kom[...]<jupyter_text>>**Note:** Dataset contains not only the coordinates of the stations but also their names. In field tasks, the performer will come to the real object only once and must clearly understand what is required to do there.> It is always helpful to give as much information as possible so that the performer does not make a mistake.For example, attach a reference photo: from which angle you want to capture the entrance to the subway station.Otherwise, the performer may misunderstand the task and capture the object too close or too shallow. For example, capture only the door when we need a general plan, or capture a general building plan when we want a sign with the opening hours of the organization.  Input and output data formatsWe need to decide what kind of data we have as input and what data we want to receive at the output.At the input, we have:- coordinates of the object (entrance);- station name and entrance number.At the output, we want to get:- verdict - if the object was found or not;- photos of the place itself, if the object is found;- photos of the surrounding area, if the object is not found - to make sure that the performer was where;- the coordinates of the performer at the time of task execution to check that it was the right place.<jupyter_code>input_specification = {
    'coordinates': toloka.project.field_spec.StringSpec(),
    'entrance': toloka.project.field_spec.StringSpec(),
}
output_specification = {
    'verdict': toloka.project.field_spec.StringSpec(),
    'entrance_images': toloka.project.field_spec.ArrayFileSpec(required=False),
    'around_images': toloka.project.field_spec.ArrayFileSpec(required=False),
    'performer_coordinates':  toloka.project.field_spec.CoordinatesSpec(current_location=True),
}<jupyter_output><empty_output><jupyter_text>Create instructions for performersIt is essential to prepare detailed instructions covering all the corner cases. Let's upload a prepared instruction from an HTML file and then analyze best practices for writing instruction for a task.<jupyter_code>prepared_instruction = open('instruction.html').read().strip()<jupyter_output><empty_output><jupyter_text>For field tasks, a good start for the instruction is configuring a device.      <img src="./img/instruction1.png"         alt="How to configure device"  width="800">        Figure 5. How to configure device  Next, we describe the steps for completing the task. Be sure to provide exact steps what performers should do if something went wrong: there is no requested object or limited access to it, or something else went wrong.      <img src="./img/instruction2.png"         alt="Steps for performers"  width="800">        Figure 6. Steps for performers  Then, it is always helpful to include requirements for photographs or other information you require from the performer.Usually, these requirements match with rules on how you will check the task.      <img src="./img/instruction3.png"         alt="Photo requirements"  width="800">        Figure 7. Photo requirements  Another best practice is to provide examples of correctly taken photos and photos that will not be accepted in the task.      <img src="./img/instruction4.png"         alt="Photo examples"  width="800">        Figure 8. Photo examples    Project interfaceIn the cell below, we define some UI elements of the project, such as the task's title, links, text fields, buttons, conditions. We use [template builder](https://toloka.github.io/toloka-kit/source/toloka.client.project.template_builder.html) (`tb`) instance from `toloka-kit`.>**Note:** You can also create an interface for this task in [Template Builder](https://clck.ru/SMCHv) web version using our [documentation](https://yandex.com/support/toloka-tb/index.html).<jupyter_code># Project title 
header = tb.view.MarkdownViewV1(content='# Cleanliness at the entrance to the metro:\n---')
# Name of metro station and number of the entrance
entrance_name = tb.view.TextViewV1(label='Entrance name:', content=tb.data.InputData(path='entrance'))
# Situation on the spot
# If the required entrance was found or not
workflow_options = tb.fields.ButtonRadioGroupFieldV1(
    options=[
        tb.fields.GroupFieldOption(label='I found the right entrance', value='ok'),
        tb.fields.GroupFieldOption(label='There is no required entrance', value='no_obj'),
    ],
    data=tb.data.OutputData(path='verdict'),
    validation=tb.conditions.RequiredConditionV1(hint='Choose one of the answer options'),
)

# Task interface if the required object was found by a performer
# First, we set all the elements that will be shown

# Description on what to do next
exist_header = tb.view.MarkdownViewV1(content='** Photos of the entrance from the right and left sides **\n\n_Take two photos of the entrance on the right and on the left. The photo should show the entire entrances and the floor. So that you can assess the cleanliness around the entrance to the metro._')

# Links to examples
example_links = tb.view.LinkGroupViewV1(
    links=[
        tb.view.LinkGroupViewV1.Link(
            url='https://tlk.s3.yandex.net/toloka-kit/images_for_instructions/0spatial_good1.png',
            content='Example1',
        ),
        tb.view.LinkGroupViewV1.Link(
            url='https://tlk.s3.yandex.net/toloka-kit/images_for_instructions/0spatial_good2.png',
            content='Example2',
        ),
        tb.view.LinkGroupViewV1.Link(
            url='https://tlk.s3.yandex.net/toloka-kit/images_for_instructions/0spatial_good3.png',
            content='Example3',
        ),
    ]
)

# Field for loading responses
image_loader = tb.fields.MediaFileFieldV1(
    data=tb.data.OutputData(path='entrance_images'),
    multiple=True,
    accept=tb.fields.MediaFileFieldV1.Accept(photo=True, gallery=True),
    validation=tb.conditions.RequiredConditionV1(hint='There must be at least 2 photos of the entrance: from the right and from the left'),
)

# Define the condition by which all the necessary interface elements will be shown
exist_ui = tb.helpers.IfHelperV1(
    condition=tb.conditions.EqualsConditionV1(data=tb.data.OutputData(path='verdict'), to='ok'),
    then=tb.view.ListViewV1(items=[exist_header, example_links, image_loader]),
)

# Task interface if the required object was NOT found by a performer
# First, we set all the elements that will be shown

# Description on what to do next
miss_header = tb.view.MarkdownViewV1(content='**Take 4 photos in all directions**\n\n_So that we can understand where you are and that there is no entrance to the metro here._')

# Field for loading responses
miss_image_loader = tb.fields.MediaFileFieldV1(
    data=tb.data.OutputData(path='around_images'),
    multiple=True,
    accept=tb.fields.MediaFileFieldV1.Accept(photo=True, gallery=True),
    validation=tb.conditions.RequiredConditionV1(hint='There must be at least 4 photos of the place'),
)

# Define the condition by which all the necessary interface elements will be shown
miss_ui = tb.helpers.IfHelperV1(
    condition=tb.conditions.EqualsConditionV1(data=tb.data.OutputData(path='verdict'), to='no_obj'),
    then=tb.view.ListViewV1(items=[miss_header, miss_image_loader]),
)

# Validation of the performer's geolocation
coordinates_validation = tb.conditions.AllConditionV1(
    conditions=[
        # Check that geolocation reading is generally possible
        tb.conditions.RequiredConditionV1(
            data=tb.data.OutputData(path='performer_coordinates'),
            hint="Couldn't get your coordinates. Please enable geolocation.",
        ),
        # Check that the performer is close enough to the required location
        tb.conditions.DistanceConditionV1(
            from_=tb.data.LocationData(),
            to_=tb.data.InputData(path='coordinates'),
            max=500,
            hint='You are too far from the entrance.',
        ),
    ]
)

# Plugin to make tasks look nice in the performer's interface
task_width_plugin = tb.plugins.TolokaPluginV1(
    layout = tb.plugins.TolokaPluginV1.TolokaPluginLayout(
        kind='scroll', 
        task_width=400,
    )
)

# Plugin for writing device coordinates at the time of task's execution to the output
coordinates_save_plugin = tb.plugins.TriggerPluginV1(
    fire_immediately=True,
    action=tb.actions.SetActionV1(
        data=tb.data.OutputData(path='performer_coordinates'),
        payload=tb.data.LocationData()
    ),
)

# How performers will see the task
project_interface = toloka.project.view_spec.TemplateBuilderViewSpec(
    config=tb.TemplateBuilder(
        view=tb.view.ListViewV1(
            items=[header, entrance_name, workflow_options, exist_ui, miss_ui],
            validation=coordinates_validation,
        ),
        plugins=[task_width_plugin, coordinates_save_plugin]
    ),
)

print('interface prepared')<jupyter_output><empty_output><jupyter_text>The performer will see the interface like this:        <img src="./img/performer_interface1.jpg"         alt="Photo examples"  width="350">        <img src="./img/performer_interface2.jpg"         alt="Photo examples"  width="350">        <img src="./img/performer_interface3.jpg"         alt="Photo examples"  width="350">          Figure 9. How performer will see your task    Create a project in TolokaFinally, we can create an instance of the [Project class](https://toloka.github.io/toloka-kit/source/toloka.client.project.htmlmodule-toloka.client.project) and send it to Toloka or Toloka Sandbox.The project with all the instructions and interface will appear in your [Toloka Requester's account](https://toloka.yandex.com/requester).<jupyter_code>project = toloka.project.Project(
    public_name='Cleanliness of metro entrances',
    public_description='Take two photos of the entrance on the right and on the left. The photo should show the entire entrances and the floor. So that you can assess the cleanliness around the entrance to the metro.',
    public_instructions=prepared_instruction,
    # We indicate that this task is selected by the performer on the map. 
    # Tasks without reference to the map are displayed simply as a list.
    assignments_issuing_type=toloka.project.Project.AssignmentsIssuingType.MAP_SELECTOR,
    # We will indicate how the title of the task and description will be displayed on the map
    assignments_issuing_view_config=toloka.project.Project.AssignmentsIssuingViewConfig(
        title_template='Photo metro entrance',  # Set title as a constant
        description_template='{{inputParams["entrance"]}}',  # Set description from the input parameters
                                                            # That way we can have different description for each point on the map
    ),
    task_spec=toloka.project.task_spec.TaskSpec(
        input_spec=input_specification,
        output_spec=output_specification,
        view_spec=project_interface
    ),
)

project = toloka_client.create_project(project)
print(f'Created project with id {project.id} https://toloka.yandex.com/requester/project/{project.id}')
# For Sandbox use:
# print(f'Created project with id {project.id} https://sandbox.toloka.yandex.com/requester/project/{project.id}')<jupyter_output><empty_output><jupyter_text>Create a poolHere we create an instance of the [Pool class](https://toloka.github.io/toloka-kit/source/toloka.client.pool.html) and send it to Toloka or Toloka Sandbox.The pool is a set of tasks sent out to performers. Learn more about working with a [pool](https://yandex.com/support/toloka-requester/concepts/pool-main.html).<jupyter_code># Set up a pool instance
pool = toloka.pool.Pool(
    project_id=project.id,
    private_name='Pool-1',
    may_contain_adult_content=False,
    will_expire=datetime.datetime.utcnow() + datetime.timedelta(days=3),
    reward_per_assignment=0.5,
    assignment_max_duration_seconds=60 * 60 * 2,  # We give 2 hours to complete the task, 
                                                  # So that performer has time to book a task, get there and complete it.
    auto_accept_solutions=False,    # We will check the completed tasks manually before paying for them.
    auto_accept_period_day=3,       # Number of days to determine if we pay
    # Only performers from the Toloka mobile application are allowed to pick field tasks
    filter=(
        (toloka.filter.ClientType == toloka.filter.ClientType.ClientType.TOLOKA_APP)&
        (toloka.filter.Languages.in_('EN'))
    ),
    defaults=toloka.pool.Pool.Defaults(
        # Overlap is not used for most field tasks
        default_overlap_for_new_task_suites=1,  
        default_overlap_for_new_tasks=1,
    ),
)

pool = toloka_client.create_pool(pool)
print(f'Created pool with id {pool.id}  https://toloka.yandex.com/requester/project/{pool.project_id}/pool/{pool.id}')
# For Sandbox use:
# print(f'Created pool with id {pool.id}  https://sandbox.toloka.yandex.com/requester/project/{pool.project_id}/pool/{pool.id}')<jupyter_output><empty_output><jupyter_text>Add tasks and run the poolTo add tasks and bind them to map coordinates, you need to create instances of [TaskSuite](https://toloka.github.io/toloka-kit/source/toloka.client.htmlmodule-toloka.client.task_suite) class. Only `TaskSuites` allow bindings with geolocation.<jupyter_code>task_suites = [
    toloka.task_suite.TaskSuite(
        pool_id=pool.id,
        latitude=round(row['latitude'], 10),  # First number in a pair of coordinates
        longitude=round(row['longitude'], 10),  # Second number
        overlap=1,
        tasks=[
            toloka.task.Task(input_values={
                'entrance': row['name'],
                'coordinates': f"{round(row['latitude'], 10)}, {round(row['longitude'], 10)}"
            })
        ],
    )
    for i, row in dataset.iterrows()]

task_suites = toloka_client.create_task_suites(task_suites)

pool_id = pool.id
toloka_client.open_pool(pool.id)
print(f'Opened pool with id {pool.id} https://toloka.yandex.ru/requester/project/{pool.project_id}/pool/{pool.id}')
# For Sandbox use:
# print(f'Opened pool with id {pool.id} https://sandbox.toloka.yandex.ru/requester/project/{pool.project_id}/pool/{pool.id}')<jupyter_output><empty_output><jupyter_text>Get results and check themValidation of field tasks differs from validation of other types of tasks.We suggest using the following rules for field task validation:1. First, you need to check that the performer came where you wanted. We did this in the task interface by comparing the device coordinates with the required coordinates.     But it will be helpful to cross-check that the user has attached real photographs of the place you required. For example, check panorama views on Yandex or Google Maps. Or an old photo of the same place, if you have one.2. Secondly, you need to remember what you pay the performer, even if the object does not exist on the spot. It is not the fault of the performer.    The performer came and verified this information for you, and for that work should be paid with the same reward.3. In the third, field tasks usually converge much slower, and there is no point in waiting for the entire pool to close. We suggest retrieving completed tasks from the pool and sending them for verification periodically.     This allows you to collect information faster. And the performers get the well-deserved reward more quickly. If you interrupted the execution of this notebook, uncomment the code below and include your `Pool ID`.<jupyter_code># pool_id = '23461858'<jupyter_output><empty_output><jupyter_text>Let's get all completed but not yet verified tasks from our pool:<jupyter_code>results_list = []

for assignment in toloka_client.get_assignments(pool_id=pool_id, status=toloka.assignment.Assignment.SUBMITTED):
    for task, solution in zip(assignment.tasks, assignment.solutions):
        results_list.append({
            'assignment_id': assignment.id,
            'input_values': task.input_values,
            'output_values': solution.output_values
        })

print(f'New results received: {len(results_list)}' if len(results_list) > 0 else 'Not received any new results yet, try to run this cell later.')

results_iter = iter(results_list)<jupyter_output><empty_output><jupyter_text>If you get at least one result, you can run the cells below.Let's prepare everything we need to get the results:<jupyter_code>!pip install ipyplot
from PIL import Image
import ipyplot
import io<jupyter_output><empty_output><jupyter_text>Now we can execute the cells below. In the first one, you see all the information received. In the second and third, you can accept or reject the assignment.You can execute these cells several times.<jupyter_code>res = next(results_iter, None)

if res is not None:
    images = []
    for id in res['output_values']['entrance_images']:
        out_b = io.BytesIO()
        toloka_client.download_attachment(id, out_b)
        images.append(Image.open(out_b).convert('RGBA'))

    print(f"Entrance name:\t\t{res['input_values']['station']}")
    print(f"Object found:\t\t{res['output_values']['verdict']}")
    print(f"Object coordinates:\t{res['input_values']['coordinates']}")
    print(f"Performer coordinates:\t{res['output_values']['performer_coordinates']}")
    ipyplot.plot_images(
        images,
        max_images=10,
        img_width=600,
    )
else:
    print('No more results')<jupyter_output><empty_output><jupyter_text>Now let's try to reject or accept the completed assignment. Remember that it will be automatically accepted after the time specified when creating the pool. If you try to accept or reject the same task twice, you will get an error.<jupyter_code># If you want to accept assignment
if res is not None:
    updated_assignment = toloka_client.accept_assignment(res['assignment_id'], 'Well done!')
    print(updated_assignment.status)
# If you want to reject assignment
if res is not None:
    updated_assignment = toloka_client.reject_assignment(res['assignment_id'], 'Type your issue here')
    print(updated_assignment.status)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08750050450014125, 'fraction_numerical': 0.019857125559995158, 'mean_word_length': 4.053640628186824, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 73, 'https://': 29, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '365437', 'license': 'Apache-2.0', 'n_tokens_mistral': 7490, 'n_tokens_neox': 7077, 'n_words': 2426, 'path': 'examples/2.spatial_crowdsourcing/0.simplest_example/spatial_crowdsourcing.ipynb', 'repo_name': 'Losik/toloka-kit', 'size': '24777'}