> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/sdgm/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/sdgm/_mcp/server.

# Buy It Again Recommendations

Recommend products a customer has bought before and may buy again in the next 30 days. This is a ranking decision for each user, not a prediction that a purchase will certainly occur.

## Data and relationships

The example assumes a Kumo Engine `graph` and a matching `snapshot` with these tables:

| Table          | Required columns                  | Purpose                            |
| -------------- | --------------------------------- | ---------------------------------- |
| `users`        | `user_id` as the primary key      | Defines the users to score.        |
| `items`        | `item_id` as the primary key      | Defines the product candidates.    |
| `transactions` | `user_id`, `item_id`, `timestamp` | Records purchases and their times. |

Connect `transactions.user_id` to `users.user_id` and `transactions.item_id` to `items.item_id`. Mark `transactions.timestamp` as the time column. Keep a transaction extract from the same data as the snapshot for the history filter below. The filter uses only purchases at or before the prediction time.

## Rank future purchases

This Python query asks Kumo Engine to rank items each user may purchase during the next 30 days. `LIST_DISTINCT` makes repeated purchases of an item one target item in a window. The query includes first purchases, so the next step selects the repeat candidates.

```python
from datetime import datetime
from kumo.engine import PredictiveQuery

query_text = (
    "PREDICT LIST_DISTINCT(transactions.item_id, 0, 30, days) "
    "RANK TOP 50 FOR EACH users.user_id"
)
query = PredictiveQuery(graph, query_text)
query.validate()
print(query.get_task_type())  # temporal_link_prediction

prediction_time = datetime(2024, 4, 30)  # Replace with your chosen UTC time.
prediction_table = query.generate_prediction_table(
    snapshot, prediction_time=prediction_time
)
ranked = trainer.predict(
    prediction_table, snapshot=snapshot, num_classes_to_return=50
).frame
```

Here `trainer` is a fitted model for this query. The prediction table selects users and the requested time; it does not train the model. For a live run, choose a time after the latest observed purchase. The returned `ENTITY` identifies a user, `CLASS` identifies an item, and `SCORE` orders candidate items for that user. A score is not a guaranteed purchase probability.

## Keep previously purchased items

Use `transactions`, a pandas DataFrame from the graph's source extract, to retain only user and item pairs purchased by the prediction time. This step is Python postprocessing; the predictive query itself does not enforce the repeat condition.

```python
history = transactions.loc[
    transactions["timestamp"] <= prediction_time, ["user_id", "item_id"]
].drop_duplicates()

repeat_candidates = (
    ranked.merge(
        history,
        left_on=["ENTITY", "CLASS"],
        right_on=["user_id", "item_id"],
        how="inner",
    )
    .sort_values(["ENTITY", "SCORE"], ascending=[True, False])
    [["ENTITY", "CLASS", "SCORE"]]
)
```

Each remaining row is a product the user previously bought, ranked by its score for a purchase in the next 30 days. You can use the rows to order a replenishment shelf or choose candidates for a message. `RANK TOP 50` limits the general purchase candidates before the history filter runs, so a user may have fewer than 50 repeat candidates or none. A first purchase in the future window can be a training label, but it cannot pass the history filter for a buy again recommendation.