# Proposed contract, version 1: endpoints, errors, paging, idempotency, security and the capability of every
# operation. Resource schemas were initialized from the data model (docs/06-data) and are kept aligned with it; input
# schemas (*Input, *Request) and views are specific to this contract. Status: proposal for review, not confirmed design.
# Conventions: docs/05-interfaces/choosing-an-interface.md. Vendor extensions: x-moonitoring-audience (who calls),
# x-moonitoring-capability (the capability checked), x-moonitoring-entitlement (the entitlement feature checked),
# x-moonitoring-requirements (the requirements of 1.requisiti-raffinati.txt the operation serves).
openapi: 3.1.0
info:
  title: Grant Research API
  version: 1.0.0
  description: 'The farmer''s side of MooGrant (req 210 to 214): the research that owns everything a farmer leaves
    with us, the farm as the journey describes it, the results that match its comune and purposes, the count that
    follows the journey, and what the farmer did with each call. A farmer works as a guest, with a private credential
    that lasts 90 days from the last visit, or signed in through access.'
servers:
- url: https://grant.moonitoring.com/research/v1
  description: Prod
- url: https://stage-grant.moonitoring.com/research/v1
  description: Stage, through IAP
security:
- bearer: []
tags:
- name: Research
  description: The farmer's research and farm
- name: Results
  description: Matching calls and the count
- name: Actions
  description: Saved and dismissed calls
paths:
  /research:
    get:
      summary: Read my research
      operationId: getResearch
      description: 'From the token or the guest cookie, never from an id in the request. 404 when the browser has
        none: the journey starts.'
      tags:
      - Research
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResearchView'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '212'
      - '213'
    delete:
      summary: Delete my research
      operationId: deleteResearch
      description: Cascades to the farm, documents, facts, checks and actions; storage is purged first.
      tags:
      - Research
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      responses:
        '204':
          description: Done.
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '212'
  /research/farm:
    post:
      summary: Save the farm
      operationId: saveResearchFarm
      description: Step three of the journey. Without a research, creates a guest one and sets its cookie. A guest
        research is updated after locking its owner; a concurrent claim or expiry answers 409.
      tags:
      - Research
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResearchFarmInput'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResearchView'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '212'
      - '213'
      - '213.3'
      - '212.3'
      - '1207'
      - '1211'
      - '1206'
  /research/contact:
    patch:
      summary: Save the contact
      operationId: saveResearchContact
      tags:
      - Research
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactInput'
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Research'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '212.5'
      - '212.6'
  /research/claim:
    post:
      summary: Claim a guest research
      operationId: claimResearch
      description: 'On sign-in: the account takes the guest research and, when the account has no Farm Organization
        yet, a pending one is created from the research''s farm (D21); it never overwrites an account farm.'
      tags:
      - Research
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      responses:
        '204':
          description: Done.
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '212.3'
      - '212.2'
  /results:
    get:
      summary: List the matching calls
      operationId: listResults
      description: Requires a research with a phone, else 401 with onboarding_required. The URL's comune and purposes
        win over the saved farm; results are evaluated at the current instant and never cached.
      tags:
      - Results
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      - name: istat
        in: query
        required: false
        schema:
          type: string
        description: ISTAT code of the comune; default the farm's.
      - name: purposes
        in: query
        required: false
        schema:
          type: string
        description: Purpose groups, comma-separated; default the farm's.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Results'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '202'
      - '213'
      - '1208'
      - '1212'
      - '218'
      - '1206'
  /results/count:
    get:
      summary: Count the matching calls
      operationId: countResults
      description: The same candidate filters as results, without ranking or document checks; without a comune,
        every candidate with a national search area.
      tags:
      - Results
      parameters:
      - name: istat
        in: query
        required: false
        schema:
          type: string
      - name: purposes
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GrantCount'
        '429':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/TooManyRequests
      security: []
      x-moonitoring-audience: public
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '202'
      - '1212'
  /results/{call_id}/adviser-email:
    get:
      summary: Prepare the adviser email
      operationId: getAdviserEmail
      tags:
      - Results
      parameters:
      - name: farm-research
        in: cookie
        required: false
        schema:
          type: string
        description: 'The guest credential of a research without an account: 32 random bytes, HttpOnly, renewed
          on every visit for 90 days. A signed-in farmer presents the bearer token instead (req 212).'
      - name: call_id
        in: path
        required: true
        schema:
          type: string
        description: The call's public id.
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdviserEmail'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      security:
      - {}
      - bearer: []
      x-moonitoring-audience: any
      x-moonitoring-capability: null
      x-moonitoring-requirements:
      - '202'
  /actions:
    get:
      summary: List my actions on calls
      operationId: listCallActions
      description: 'Signed-in farmers only: saved and dismissed calls.'
      tags:
      - Actions
      parameters:
      - name: call
        in: query
        required: false
        schema:
          type: string
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/CallAction'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: organization
      x-moonitoring-capability: grant.call_actions.manage
      x-moonitoring-requirements:
      - '214'
  /calls/{call_id}/actions:
    post:
      summary: Record an action on a call
      operationId: recordCallAction
      tags:
      - Actions
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: call_id
        in: path
        required: true
        schema:
          type: string
        description: The call's public id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CallActionInput'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CallAction'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: organization
      x-moonitoring-capability: grant.call_actions.manage
      x-moonitoring-requirements:
      - '214'
components:
  schemas:
    Research:
      type: object
      description: 'The unit of ownership: one account or one guest credential, plus contact (req 212).'
      properties:
        id:
          type: string
          format: uuid
        owner_user:
          type: string
          format: uuid
          description: ID of the User, when signed in.
        organization:
          type: string
          format: uuid
          description: ID of the Farm Organization the research is bound to (req 212.8, D21); created pending at
            sign-up, empty for a guest.
        guest:
          type: boolean
          description: True for a guest research.
        guest_expires_at:
          type: string
          format: date-time
        contact_phone:
          type: string
        contact_email:
          type: string
          format: email
        stage:
          type: string
          enum:
          - farm
          - contacts
          - results
          description: 'Where the farmer is: no farm yet, farm without a phone, or results.'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - guest
      - stage
      - created_at
      - updated_at
    ResearchFarm:
      type: object
      description: The farm of a research as the journey describes it; the profile never affects matching (req 213).
      properties:
        id:
          type: string
          format: uuid
          description: Same as the research.
        name:
          type: string
        municipality:
          type: string
          description: ISTAT code.
        species:
          type: array
          items:
            type: string
            enum:
            - bovini
            - bufalini
            - ovini
            - caprini
            - suini
            - equini
            - avicoli
            - conigli
            - api
        purpose_groups:
          type: array
          items:
            type: string
            enum:
            - equipment
            - buildings
            - environment
            - animals
            - sales
            - people
            - finance
            - innovation
            - all
        herds:
          type: array
          items:
            type: object
            properties:
              species:
                type: string
              head_count:
                type: integer
              orientation:
                type: string
                enum:
                - latte
                - carne
                - entrambi
            required:
            - species
            - head_count
        problems:
          type: array
          items:
            type: string
        software:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - municipality
      - species
      - purpose_groups
      - created_at
      - updated_at
    CallAction:
      type: object
      description: What a farmer did with a call; append-only (req 214).
      properties:
        id:
          type: string
          format: uuid
        research:
          type: string
          format: uuid
        call:
          type: string
        action:
          type: string
          enum:
          - open
          - save
          - dismiss
          - clear
        created_at:
          type: string
          format: date-time
      required:
      - id
      - research
      - call
      - action
      - created_at
    ResultCard:
      type: object
      description: The card of a published call, evidence-free.
      properties:
        id:
          type: string
        title:
          type: string
        authority:
          type: string
        authority_level:
          type: string
        funded_activity:
          type: string
        beneficiary_phrase:
          type: string
        key_condition:
          type: string
        purpose_tags:
          type: array
          items:
            type: string
      required:
      - id
      - title
    ResultCall:
      type: object
      description: One call in a farm's results, ranked explicit first.
      properties:
        call:
          $ref: '#/components/schemas/ResultCard'
        availability:
          type: string
          enum:
          - open
          - upcoming
          - closed
          - to_confirm
        next_deadline:
          type: string
        farm_fit:
          type: string
          enum:
          - explicit
          - generic
        preference:
          type: string
          enum:
          - save
          - dismiss
        check:
          type: string
          enum:
          - excluded
          - candidate
          - none
          description: The document check's verdict, when the farmer checked the call.
        not_verified_by_documents:
          type: array
          items:
            type: string
      required:
      - call
      - availability
      - farm_fit
    Results:
      type: object
      description: The matching calls of a farm at an explicit time (req 1208, 1212).
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/ResultCall'
        remaining_count:
          type: integer
          description: Cards after document exclusions.
        evaluated_at:
          type: string
          format: date-time
        next_cursor:
          type: string
      required:
      - items
      - remaining_count
      - evaluated_at
    ResearchView:
      type: object
      description: A research with its farm.
      properties:
        research:
          $ref: '#/components/schemas/Research'
        farm:
          $ref: '#/components/schemas/ResearchFarm'
      required:
      - research
    ResearchFarmInput:
      type: object
      description: 'Creates or updates the farm: a guest research is created on the first save with its cookie;
        an account farm changes only here when the account has none yet (req 213).'
      properties:
        name:
          type: string
        municipality:
          type: string
          description: ISTAT code.
        species:
          type: array
          items:
            type: string
            enum:
            - bovini
            - bufalini
            - ovini
            - caprini
            - suini
            - equini
            - avicoli
            - conigli
            - api
        purpose_groups:
          type: array
          items:
            type: string
            enum:
            - equipment
            - buildings
            - environment
            - animals
            - sales
            - people
            - finance
            - innovation
            - all
        herds:
          type: array
          items:
            type: object
            properties:
              species:
                type: string
              head_count:
                type: integer
              orientation:
                type: string
                enum:
                - latte
                - carne
                - entrambi
            required:
            - species
            - head_count
        problems:
          type: array
          items:
            type: string
        software:
          type: array
          items:
            type: string
      required:
      - name
      - municipality
      - species
      - purpose_groups
    ContactInput:
      type: object
      description: Phone (required before results) and optional email; never farm fields (req 212.5, 212.6).
      properties:
        contact_phone:
          type: string
        contact_email:
          type: string
          format: email
    CallActionInput:
      type: object
      description: Records what the farmer did with the call.
      properties:
        action:
          type: string
          enum:
          - open
          - save
          - dismiss
          - clear
      required:
      - action
    GrantCount:
      type: object
      description: Discovery feedback, never eligibility.
      properties:
        count:
          type: integer
      required:
      - count
    AdviserEmail:
      type: object
      description: A prepared email, with the call's link, that the farmer sends from their own mailbox; it names
        the call, not the farm.
      properties:
        subject:
          type: string
        body:
          type: string
        text:
          type: string
      required:
      - subject
      - body
      - text
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'The short-lived token signed by `access`: an Organization user token, a staff token or a client
        token, as the operation''s audience says.'
