# `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.Registration.t(),
  keyword()
) :: {:ok, Ltix.OAuth.Client.t()} | {:error, Exception.t()}
```

Acquire an OAuth token for the grade service.

Accepts a `%LaunchContext{}` or a `%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.

## From a launch context

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

## From a registration

    {:ok, client} = Ltix.GradeService.authenticate(registration,
      endpoint: %AgsEndpoint{
        lineitems: "https://lms.example.com/lineitems",
        scope: ["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 `[]`.

## Options (registration)

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

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

# `authenticate!`

```elixir
@spec authenticate!(
  Ltix.LaunchContext.t() | Ltix.Registration.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

* `: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` (`t:String.t/0`) - ISO 8601 with timezone.

* `:end_date_time` (`t:String.t/0`) - ISO 8601 with timezone.

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

* `:extensions` (`t:map/0`) - Extension properties keyed by fully qualified URLs. The default value is `%{}`.

# `delete_line_item`

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

Delete a line item.

Accepts a `%LineItem{}` struct or a URL string. Returns `:ok` on
success.

By default, refuses to delete the platform-coupled line item
(the `lineitem` URL from the launch claim). Pass `force: true`
to override.

## Options

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

# `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

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

# `get_results`

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

Fetch results for a line item.

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

## Options

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

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

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

# `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

* `: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.

# `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 `{lineitem}/scores`. When no `:line_item` option
is given, uses the endpoint's `lineitem` URL (coupled flow).

## Options

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

# `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.

---

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