# `Ltix.GradeService`
[🔗](https://github.com/DecoyLex/ltix/blob/main/lib/ltix/grade_service.ex#L1)

Manage line items, post scores, and read results from a platform's
gradebook using the
[Assignment and Grade Services (AGS) v2.0](https://www.imsglobal.org/spec/lti-ags/v2p0/).

AGS has two workflows depending on the launch claim.

**Coupled.** The platform creates a single line item for the resource
link. The tool posts scores against that line item directly.

    {:ok, client} = Ltix.GradeService.authenticate(launch_context)
    :ok = Ltix.GradeService.post_score(client, score)

**Programmatic.** The tool manages its own line items and can create,
update, or delete them.

    {:ok, client} = Ltix.GradeService.authenticate(launch_context)
    {:ok, items} = Ltix.GradeService.list_line_items(client)
    {:ok, item} = Ltix.GradeService.create_line_item(client,
      label: "Quiz 1", score_maximum: 100
    )
    :ok = Ltix.GradeService.post_score(client, score, line_item: item)

# `authenticate`

```elixir
@spec authenticate(Ltix.LaunchContext.t() | Ltix.Registerable.t(), keyword()) ::
  {:ok, Ltix.OAuth.Client.t()} | {:error, Exception.t()}
```

Acquire an OAuth token for the grade service.

Accepts a `%LaunchContext{}` or any struct implementing `Ltix.Registerable`
(including `Ltix.Registration`). With a launch context, the endpoint and
scopes are extracted from the AGS claim. With a registration, pass the
endpoint via the `:endpoint` option, built with
`Ltix.LaunchClaims.AGSEndpoint.new/1`.

## From a launch context

    {:ok, client} = Ltix.GradeService.authenticate(launch_context)

## From a registration

    {:ok, client} = Ltix.GradeService.authenticate(registration,
      endpoint:
        AGSEndpoint.new!(
          line_items: "https://lms.example.com/lineitems",
          scopes: ["https://purl.imsglobal.org/spec/lti-ags/scope/lineitem"]
        )
    )

## Options (launch context)

* `:req_options` (`t:keyword/0`) - Options passed through to `Req.request/2`. The default value is `[]`.

* `:local_network_access` (`nil` | `:none` | `:local` | `:loopback`) - Addresses platform requests may connect to besides public ones. `:local`
  adds private network addresses, and `:loopback` adds those and this
  machine. Defaults to `config :ltix, :local_network_access`, then `:none`.

* `:storage_adapter` (`t:module/0`) - Module implementing `Ltix.StorageAdapter`, which signs token requests. Defaults to the application configuration.

* `:storage_adapter_options` (`t:keyword/0`) - Options passed to the storage adapter's signing callbacks. The default value is `[]`.

* `:require_all_scopes` (`t:boolean/0`) - Return a `ScopeMismatch` error unless every scope the endpoints need was granted. The default value is `false`.

## Options (registration)

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Required. AGS endpoint struct.

* `:deployment` (`t:term/0`) - The deployment the token is for, as a struct implementing `Ltix.Deployable` or a deployment ID. Token requests include it.

* `:req_options` (`t:keyword/0`) - Options passed through to `Req.request/2`. The default value is `[]`.

* `:local_network_access` (`nil` | `:none` | `:local` | `:loopback`) - Addresses platform requests may connect to besides public ones. `:local`
  adds private network addresses, and `:loopback` adds those and this
  machine. Defaults to `config :ltix, :local_network_access`, then `:none`.

* `:storage_adapter` (`t:module/0`) - Module implementing `Ltix.StorageAdapter`, which signs token requests. Defaults to the application configuration.

* `:storage_adapter_options` (`t:keyword/0`) - Options passed to the storage adapter's signing callbacks. The default value is `[]`.

* `:require_all_scopes` (`t:boolean/0`) - Return a `ScopeMismatch` error unless every scope the endpoints need was granted. The default value is `false`.

# `authenticate!`

```elixir
@spec authenticate!(Ltix.LaunchContext.t() | Ltix.Registerable.t(), keyword()) ::
  Ltix.OAuth.Client.t()
```

Same as `authenticate/2` but raises on error.

# `create_line_item`

```elixir
@spec create_line_item(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Ltix.GradeService.LineItem.t()} | {:error, Exception.t()}
```

Create a new line item.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:label` (`t:String.t/0`) - Required. Human-readable label.

* `:score_maximum` (`t:number/0`) - Required. Maximum score (must be > 0).

* `:resource_link_id` (`t:String.t/0`) - Bind to a resource link.

* `:resource_id` (`t:String.t/0`) - Tool's resource identifier.

* `:tag` (`t:String.t/0`) - Qualifier tag.

* `:start_date_time` (struct of type `DateTime`) - When the line item starts accepting submissions.

* `:end_date_time` (struct of type `DateTime`) - When the line item stops accepting submissions.

* `:grades_released` (`t:boolean/0`) - Hint about releasing grades.

* `:extensions` (`nil` | `t:map/0`) - Extension properties keyed by fully qualified URLs, given as strings or atoms. The default value is `%{}`.

# `create_line_item!`

```elixir
@spec create_line_item!(Ltix.OAuth.Client.t(), keyword()) ::
  Ltix.GradeService.LineItem.t()
```

Same as `create_line_item/2` but raises on error.

# `delete_line_item`

```elixir
@spec delete_line_item(Ltix.OAuth.Client.t(), keyword()) ::
  :ok | {:error, Exception.t()}
```

Delete a line item.

Returns `:ok` on success.

By default, returns an error for the platform-coupled line item
(the `line_item` URL from the launch claim). Pass `force: true`
to override.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:line_item` (`t:String.t/0` | struct of type `Ltix.GradeService.LineItem`) - Line item URL or struct. Defaults to the endpoint's `line_item`.

* `:force` (`t:boolean/0`) - Delete even if this is the coupled line item from the launch claim. The default value is `false`.

# `delete_line_item!`

```elixir
@spec delete_line_item!(Ltix.OAuth.Client.t(), keyword()) :: :ok
```

Same as `delete_line_item/2` but raises on error.

# `get_line_item`

```elixir
@spec get_line_item(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Ltix.GradeService.LineItem.t()} | {:error, Exception.t()}
```

Fetch a single line item.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:line_item` (`t:String.t/0` | struct of type `Ltix.GradeService.LineItem`) - Line item URL or struct. Defaults to the endpoint's `line_item`.

# `get_line_item!`

```elixir
@spec get_line_item!(Ltix.OAuth.Client.t(), keyword()) ::
  Ltix.GradeService.LineItem.t()
```

Same as `get_line_item/2` but raises on error.

# `list_line_items`

```elixir
@spec list_line_items(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, [Ltix.GradeService.LineItem.t()]} | {:error, Exception.t()}
```

Fetch all line items from the container endpoint.

Follows all `rel="next"` pagination links and returns a flat list
of `%LineItem{}` structs.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:resource_link_id` (`t:String.t/0`) - Filter to line items bound to this resource link.

* `:resource_id` (`t:String.t/0`) - Filter by the tool's resource identifier.

* `:tag` (`t:String.t/0`) - Filter by tag.

* `:per_page` (`t:integer/0`) - Page size hint.

* `:max_line_items` (`t:integer/0` | `:infinity`) - Safety limit for eager fetch. Returns a `CollectionTooLarge` error if exceeded. Set to `:infinity` to disable, or use `stream_line_items/2` for large gradebooks. The default value is `10000`.

# `list_line_items!`

```elixir
@spec list_line_items!(Ltix.OAuth.Client.t(), keyword()) :: [
  Ltix.GradeService.LineItem.t()
]
```

Same as `list_line_items/2` but raises on error.

# `list_results`

```elixir
@spec list_results(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, [Ltix.GradeService.Result.t()]} | {:error, Exception.t()}
```

Fetch results for a line item.

GETs `{line_item}/results`. Follows all `rel="next"` pagination links
and returns a list of `%Result{}` structs.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:line_item` (`t:String.t/0` | struct of type `Ltix.GradeService.LineItem`) - Line item URL or struct. Defaults to the endpoint's `line_item`.

* `:user_id` (`t:String.t/0`) - Filter results to a single user.

* `:per_page` (`t:integer/0`) - Page size hint.

* `:max_results` (`t:integer/0` | `:infinity`) - Safety limit for eager fetch. Returns a `CollectionTooLarge` error if exceeded. Set to `:infinity` to disable, or use `stream_results/2` for large courses. The default value is `10000`.

# `list_results!`

```elixir
@spec list_results!(Ltix.OAuth.Client.t(), keyword()) :: [
  Ltix.GradeService.Result.t()
]
```

Same as `list_results/2` but raises on error.

# `post_score`

```elixir
@spec post_score(Ltix.OAuth.Client.t(), Ltix.GradeService.Score.t(), keyword()) ::
  :ok | {:error, Exception.t()}
```

Post a score for a user.

The score is POSTed to `{line_item}/scores`. When no `:line_item` option
is given, uses the endpoint's `line_item` URL (coupled flow).

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:line_item` (`t:String.t/0` | struct of type `Ltix.GradeService.LineItem`) - Line item URL or struct. Defaults to the endpoint's `line_item`.

# `post_score!`

```elixir
@spec post_score!(Ltix.OAuth.Client.t(), Ltix.GradeService.Score.t(), keyword()) ::
  :ok
```

Same as `post_score/3` but raises on error.

# `stream_line_items`

```elixir
@spec stream_line_items(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Enumerable.t()} | {:error, Exception.t()}
```

Fetch line items as a lazy stream.

Returns `{:ok, stream}` where each element is a `%LineItem{}`. Use this
for large gradebooks where you want to process line items incrementally
or stop early. `list_line_items/2` returns the whole list. Later page
failures and parsing errors raise during enumeration.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:resource_link_id` (`t:String.t/0`) - Filter to line items bound to this resource link.

* `:resource_id` (`t:String.t/0`) - Filter by the tool's resource identifier.

* `:tag` (`t:String.t/0`) - Filter by tag.

* `:per_page` (`t:integer/0`) - Page size hint.

# `stream_line_items!`

```elixir
@spec stream_line_items!(Ltix.OAuth.Client.t(), keyword()) :: Enumerable.t()
```

Same as `stream_line_items/2` but raises on error.

# `stream_results`

```elixir
@spec stream_results(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Enumerable.t()} | {:error, Exception.t()}
```

Fetch results for a line item as a lazy stream.

Returns `{:ok, stream}` where each element is a `%Result{}`. Use this
for large courses where you want to process results incrementally or
stop early. `list_results/2` returns the whole list. Later page failures
and parsing errors raise during enumeration.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.AGSEndpoint`) - Override the endpoint stored on the client.

* `:line_item` (`t:String.t/0` | struct of type `Ltix.GradeService.LineItem`) - Line item URL or struct. Defaults to the endpoint's `line_item`.

* `:user_id` (`t:String.t/0`) - Filter results to a single user.

* `:per_page` (`t:integer/0`) - Page size hint.

# `stream_results!`

```elixir
@spec stream_results!(Ltix.OAuth.Client.t(), keyword()) :: Enumerable.t()
```

Same as `stream_results/2` but raises on error.

# `update_line_item`

```elixir
@spec update_line_item(Ltix.OAuth.Client.t(), Ltix.GradeService.LineItem.t()) ::
  {:ok, Ltix.GradeService.LineItem.t()} | {:error, Exception.t()}
```

Update a line item.

PUTs the full line item to its `id` URL. Callers should GET first to
avoid overwriting fields they did not intend to change.

# `update_line_item!`

```elixir
@spec update_line_item!(Ltix.OAuth.Client.t(), Ltix.GradeService.LineItem.t()) ::
  Ltix.GradeService.LineItem.t()
```

Same as `update_line_item/2` but raises on error.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
