# Code snippet: query-points/hybrid-formula-decay

This code snippet shows the canonical pattern for combining hybrid search fusion with business-logic ranking. The inner prefetch fuses lexical and dense results with RRF, then the outer `FormulaQuery` applies exponential decay on a `published_at` payload field to boost recent documents. The decay term is wrapped in `mult` with a `0.1` coefficient so it nudges the ranking rather than crowding out the small RRF scores. Use this pattern any time you want fusion plus recency, popularity, geo decay, or category-conditional multipliers, rather than trying to encode those signals as fusion weights.

For production: `published_at` must exist on every point, or supply `defaults` in the `FormulaQuery` to fill missing values. A datetime payload index on `published_at` keeps query latency low.

## Qdrant Server

```http
POST /collections/{collection_name}/points/query
{
    "prefetch": {
        "prefetch": [
            {
                "query": { "text": "time travel" }, // <-- BM25 text query
                "using": "title",
                "limit": 100
            },
            {
                "query": [0.01, 0.45, 0.67, ...], // <-- dense vector
                "using": "dense",
                "limit": 100
            }
        ],
        "query": { "rrf": {} },
        "limit": 100
    },
    "query": {
        "formula": {
            "sum": [
                "$score", // the fused score from the RRF prefetch
                {
                    "mult": [
                        0.1, // caps decay contribution
                        {
                            "exp_decay": {
                                "x": {
                                    "datetime_key": "published_at"
                                },
                                "target": {
                                    "datetime": "YYYY-MM-DDT00:00:00Z"
                                },
                                "scale": 15552000, // 180 days in seconds
                                "midpoint": 0.5
                            }
                        }
                    ]
                }
            ]
        }
    },
    "limit": 10
}
```

```python
from qdrant_client import QdrantClient, models

client.query_points(
    collection_name="{collection_name}",
    prefetch=models.Prefetch(
        prefetch=[
            models.Prefetch(
                query=models.TextQuery(text="time travel"),
                using="title",
                limit=100,
            ),
            models.Prefetch(
                query=[0.01, 0.45, 0.67],  # <-- dense vector
                using="dense",
                limit=100,
            ),
        ],
        query=models.RrfQuery(rrf=models.Rrf()),
        limit=100,
    ),
    query=models.FormulaQuery(
        formula=models.SumExpression(
            sum=[
                "$score",  # the fused score from the RRF prefetch
                models.MultExpression(mult=[
                    0.1,  # caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
                    models.ExpDecayExpression(
                        exp_decay=models.DecayParamsExpression(
                            x=models.DatetimeKeyExpression(datetime_key="published_at"),
                            target=models.DatetimeExpression(datetime="YYYY-MM-DDT00:00:00Z"),
                            scale=86400 * 180,  # 180 days in seconds
                            midpoint=0.5,
                        )
                    ),
                ]),
            ]
        )
    ),
    limit=10,
)
```

```typescript
import { QdrantClient } from "@qdrant/js-client-rest";

await client.query("{collection_name}", {
    prefetch: {
        prefetch: [
            {
                query: { text: "time travel" },
                using: "title",
                limit: 100,
            },
            {
                query: [0.01, 0.45, 0.67], // <-- dense vector
                using: "dense",
                limit: 100,
            },
        ],
        query: { rrf: {} },
        limit: 100,
    },
    query: {
        formula: {
            sum: [
                "$score", // the fused score from the RRF prefetch
                {
                    mult: [
                        0.1, // caps decay contribution; un-weighted decay [0, 1] would otherwise crowd out small RRF scores
                        {
                            exp_decay: {
                                x: { datetime_key: "published_at" },
                                target: { datetime: "YYYY-MM-DDT00:00:00Z" },
                                scale: 86400 * 180, // 180 days in seconds
                                midpoint: 0.5,
                            },
                        },
                    ],
                },
            ],
        },
    },
    limit: 10,
});
```

```rust
use qdrant_client::Qdrant;
use qdrant_client::qdrant::{
    DecayParamsExpressionBuilder, Expression, FormulaBuilder, PrefetchQueryBuilder, Query,
    QueryPointsBuilder, RrfBuilder,
};

client.query(
    QueryPointsBuilder::new("{collection_name}")
        .add_prefetch(
            PrefetchQueryBuilder::default()
                .add_prefetch(
                    PrefetchQueryBuilder::default()
                        .query(Query::new_text("time travel"))
                        .using("title")
                        .limit(100u64),
                )
                .add_prefetch(
                    PrefetchQueryBuilder::default()
                        .query(Query::new_nearest(vec![0.01, 0.45, 0.67]))
                        .using("dense")
                        .limit(100u64),
                )
                .query(Query::new_rrf(RrfBuilder::default()))
                .limit(100u64),
        )
        .query(
            FormulaBuilder::new(Expression::sum_with([
                Expression::score(),
                Expression::mult_with([
                    Expression::constant(0.1),
                    Expression::exp_decay(
                        DecayParamsExpressionBuilder::new(Expression::datetime_key("published_at"))
                            .target(Expression::datetime("YYYY-MM-DDT00:00:00Z"))
                            .scale(86400.0 * 180.0)
                            .midpoint(0.5),
                    ),
                ]),
            ])),
        )
        .limit(10u64),
)
.await?;
```

```java
import static io.qdrant.client.ExpressionFactory.constant;
import static io.qdrant.client.ExpressionFactory.datetime;
import static io.qdrant.client.ExpressionFactory.datetimeKey;
import static io.qdrant.client.ExpressionFactory.expDecay;
import static io.qdrant.client.ExpressionFactory.mult;
import static io.qdrant.client.ExpressionFactory.sum;
import static io.qdrant.client.ExpressionFactory.variable;
import static io.qdrant.client.QueryFactory.formula;
import static io.qdrant.client.QueryFactory.nearest;
import static io.qdrant.client.QueryFactory.rrf;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.DecayParamsExpression;
import io.qdrant.client.grpc.Points.Formula;
import io.qdrant.client.grpc.Points.MultExpression;
import io.qdrant.client.grpc.Points.PrefetchQuery;
import io.qdrant.client.grpc.Points.Query;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.Rrf;
import io.qdrant.client.grpc.Points.SumExpression;
import io.qdrant.client.grpc.Points.TextQuery;
import java.util.List;

client.queryAsync(
    QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .addPrefetch(
            PrefetchQuery.newBuilder()
                .addPrefetch(
                    PrefetchQuery.newBuilder()
                        .setQuery(Query.newBuilder().setText(TextQuery.newBuilder().setQuery("time travel")).build())
                        .setUsing("title")
                        .setLimit(100)
                        .build())
                .addPrefetch(
                    PrefetchQuery.newBuilder()
                        .setQuery(nearest(List.of(0.01f, 0.45f, 0.67f)))
                        .setUsing("dense")
                        .setLimit(100)
                        .build())
                .setQuery(rrf(Rrf.newBuilder().build()))
                .setLimit(100)
                .build())
        .setQuery(
            formula(
                Formula.newBuilder()
                    .setExpression(
                        sum(
                            SumExpression.newBuilder()
                                .addSum(variable("$score"))
                                .addSum(
                                    mult(
                                        MultExpression.newBuilder()
                                            .addMult(constant(0.1f))
                                            .addMult(
                                                expDecay(
                                                    DecayParamsExpression.newBuilder()
                                                        .setX(datetimeKey("published_at"))
                                                        .setTarget(
                                                            datetime("YYYY-MM-DDT00:00:00Z"))
                                                        .setScale(86400 * 180)
                                                        .setMidpoint(0.5f)
                                                        .build()))
                                            .build()))
                                .build()))
                    .build()))
        .setLimit(10)
        .build())
  .get();
```

```csharp
using Qdrant.Client;
using Qdrant.Client.Grpc;

await client.QueryAsync(
    collectionName: "{collection_name}",
    prefetch:
    [
        new PrefetchQuery {
            Prefetch = {
                new PrefetchQuery {
                    Query = new Query { Text = new TextQuery { Query = "time travel" } },
                    Using = "title",
                    Limit = 100
                },
                new PrefetchQuery {
                    Query = new float[] { 0.01f, 0.45f, 0.67f },
                    Using = "dense",
                    Limit = 100
                },
            },
            Query = new Rrf(),
            Limit = 100
        },
    ],
    query: new Formula
    {
        Expression = new SumExpression
        {
            Sum =
            {
                "$score", // the fused score from the RRF prefetch
                new MultExpression
                {
                    Mult =
                    {
                        0.1f, // caps decay contribution
                        Expression.FromExpDecay(
                            new()
                            {
                                X = Expression.FromDateTimeKey("published_at"),
                                Target = Expression.FromDateTime("YYYY-MM-DDT00:00:00Z"),
                                Scale = 86400 * 180, // 180 days in seconds
                                Midpoint = 0.5f
                            }
                        )
                    }
                }
            }
        }
    },
    limit: 10
);
```

```go
import (
	"context"

	"github.com/qdrant/go-client/qdrant"
)

client.Query(context.Background(), &qdrant.QueryPoints{
	CollectionName: "{collection_name}",
	Prefetch: []*qdrant.PrefetchQuery{
		{
			Prefetch: []*qdrant.PrefetchQuery{
				{
					Query: qdrant.NewQueryText(&qdrant.TextQuery{Query: "time travel"}),
					Using: qdrant.PtrOf("title"),
					Limit: qdrant.PtrOf(uint64(100)),
				},
				{
					Query: qdrant.NewQueryDense([]float32{0.01, 0.45, 0.67}),
					Using: qdrant.PtrOf("dense"),
					Limit: qdrant.PtrOf(uint64(100)),
				},
			},
			Query: qdrant.NewQueryRRF(&qdrant.Rrf{}),
			Limit: qdrant.PtrOf(uint64(100)),
		},
	},
	Query: qdrant.NewQueryFormula(&qdrant.Formula{
		Expression: qdrant.NewExpressionSum(&qdrant.SumExpression{
			Sum: []*qdrant.Expression{
				qdrant.NewExpressionVariable("$score"), // the fused score from the RRF prefetch
				qdrant.NewExpressionMult(&qdrant.MultExpression{
					Mult: []*qdrant.Expression{
						qdrant.NewExpressionConstant(0.1), // caps decay contribution
						qdrant.NewExpressionExpDecay(&qdrant.DecayParamsExpression{
							X:        qdrant.NewExpressionDatetimeKey("published_at"),
							Target:   qdrant.NewExpressionDatetime("YYYY-MM-DDT00:00:00Z"),
							Scale:    qdrant.PtrOf(float32(86400 * 180)), // 180 days in seconds
							Midpoint: qdrant.PtrOf(float32(0.5)),
						}),
					},
				}),
			},
		}),
	}),
	Limit: qdrant.PtrOf(uint64(10)),
})
```

## Qdrant Edge

```python
from qdrant_edge import (
    DecayKind,
    Expression,
    Formula,
    Fusion,
    Prefetch,
    Query,
    QueryRequest,
    TextQuery,
)

results = edge_shard.query(QueryRequest(
    prefetches=[
        Prefetch(
            prefetches=[
                Prefetch(
                    query=TextQuery("title", "time travel"),
                    limit=100,
                ),
                Prefetch(
                    query=Query.Nearest([0.01, 0.45, 0.67], using="dense"),
                    limit=100,
                ),
            ],
            query=Fusion.Rrf(k=2),
            limit=100,
        ),
    ],
    query=Formula(
        formula=Expression.Sum([
            Expression.Variable("$score"),
            Expression.Mult([
                Expression.Constant(0.1),
                Expression.Decay(
                    kind=DecayKind.Exp,
                    x=Expression.DatetimeKey("published_at"),
                    target=Expression.Datetime("YYYY-MM-DDT00:00:00Z"),
                    midpoint=0.5,
                    scale=86400.0 * 180.0,
                ),
            ]),
        ]),
    ),
    limit=10,
))
```

```rust
use qdrant_edge::*;

let results = edge_shard.query(
    QueryRequestBuilder::new(10)
        .add_prefetch(
            PrefetchBuilder::new(100)
                .add_prefetch(
                    PrefetchBuilder::new(100)
                        .query(TextQueryBuilder::new("title".try_into().unwrap(), "time travel").build())
                        .build(),
                )
                .add_prefetch(
                    PrefetchBuilder::new(100)
                        .query(ScoringQuery::Vector(QueryEnum::Nearest(NamedQuery {
                            query: vec![0.01f32, 0.45, 0.67].into(),
                            using: Some("dense".to_string()),
                        })))
                        .build(),
                )
                .query(ScoringQuery::Fusion(Fusion::Rrf { k: 2, weights: None }))
                .build(),
        )
        .query(ScoringQuery::Formula(
            Formula {
                formula: Expression::Sum(vec![
                    Expression::Variable("$score".to_string()),
                    Expression::Mult(vec![
                        Expression::Constant(0.1),
                        Expression::Decay {
                            kind: DecayKind::Exp,
                            x: Box::new(Expression::DatetimeKey(
                                "published_at".try_into().unwrap(),
                            )),
                            target: Some(Box::new(Expression::Datetime(
                                "YYYY-MM-DDT00:00:00Z".to_string(),
                            ))),
                            midpoint: Some(0.5),
                            scale: Some(86400.0 * 180.0),
                        },
                    ]),
                ]),
                defaults: Default::default(),
            }
            .try_into()?,
        ))
        .build(),
)?;
```

