# Tender Triage — Bid/No-Bid pour marchés publics FR (`dr1ms/tender-triage`) Actor

À partir d'un avis BOAMP (ou d'une URL de consultation Atexo/PLACE), télécharge le Règlement de Consultation, en extrait un schéma décisionnel par LLM, et produit un score bid/no-bid contre un profil d'entreprise. Une entrée = une décision.

- **URL**: https://apify.com/dr1ms/tender-triage.md
- **Developed by:** [Adrien](https://apify.com/dr1ms) (community)
- **Categories:** AI, SEO tools, Jobs
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 tender analyseds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Tender Triage — Bid/No-Bid pour marchés publics français

Une **primitive d'analyse, agent-native**, pour qualifier un marché public français
en une décision actionnable.

À partir d'un **avis BOAMP** (ou directement d'une **URL de consultation
Atexo/PLACE**), l'actor :

1. télécharge le **Règlement de Consultation (RC)** — accès anonyme, sans
   captcha, sans proxy ;
2. en **extrait un schéma décisionnel** par LLM (objet, procédure, lots, critères
   d'attribution et pondérations, conditions de participation, date limite,
   DUME, lieu, montant…) ;
3. produit un **verdict bid/no-bid** contre le **profil de votre entreprise**.

**Une entrée = une décision.** Conçu comme une brique composable, à permissions
limitées, **consommable par un agent IA** (MCP-ready) — pas un SaaS d'abonnement
de plus, mais un primitif que vous appelez à la demande.

### Ce que vous obtenez (par avis)

Une ligne de dataset, par avis analysé :

| Champ | Description |
|---|---|
| `decision` | `GO` / `À_ÉTUDIER` / `NO_GO` |
| `score` | Score de fit 0–1 (0 si disqualifié par une règle dure) |
| `raisons` | 1 à 3 raisons de la décision |
| `disqualifiants` | Motifs de disqualification (certif manquante, hors zone, capacité dépassée…) |
| Enrichissement | Objet, acheteur, CPV, critères d'attribution, date limite, lieu, montant, DUME, … |

La décision suit une logique en deux temps : des **règles dures** (gates) qui
disqualifient sans appel (certification obligatoire manquante, CA mini supérieur
à votre capacité, référence exigée absente, lieu hors zone confirmé), puis un
**fit doux** (CPV/secteur, géographie, taille du marché, alignement aux critères).
Les gates sont **déterministes et reproductibles** ; ils ne disqualifient que sur
un motif certain (un lieu non résolu n'élimine jamais — il part en À_ÉTUDIER).

### Entrée

```jsonc
{
  // Option A — analyser des consultations précises :
  "consultationUrls": [
    "https://marches.maximilien.fr/entreprise/consultation/123?orgAcronyme=ab"
  ],

  // Option B — laisser l'actor chercher dans BOAMP (si pas d'URLs) :
  "boampFilters": { "keywords": "voirie", "cpv": ["45233000"], "departement": ["75"] },
  "maxNotices": 100,

  // Votre profil entreprise (obligatoire) :
  "companyProfile": {
    "cpv_codes": ["45000000"],
    "certifications_detenues": ["Qualibat"],
    "references": ["Chantier de référence 1"],
    "geographies": ["75", "Île-de-France"],
    "capacite_financiere_max": 500000,
    "taille_equipe": 20
  },

  "llmTier": "haiku",   // "haiku" (défaut, économique) ou "sonnet" (plus fin)
  "maxTenders": 25      // plafond d'analyses
}
````

#### Clé API Anthropic

L'extraction utilise l'API Anthropic. Fournissez votre clé de l'une de ces façons
(elle n'apparaît jamais dans les logs) :

- **Recommandé** — variable d'environnement **secrète** `ANTHROPIC_API_KEY`
  (Actor → Settings → Environment variables → cocher *Secret*) ; ou
- le champ d'input **`anthropicApiKey`** (marqué comme secret : la plateforme le
  chiffre).

### Exemple de sortie

Un avis de travaux en Savoie (73), scoré contre une PME BTP locale titulaire
Qualibat :

```json
{
  "notice_id": "26-61689",
  "decision": "GO",
  "score": 0.90,
  "raisons": [
    "Lieu d'exécution dans la zone géographique",
    "Critères d'attribution clairs et pondérés"
  ],
  "disqualifiants": []
}
```

À l'inverse, un avis exigeant une certification **obligatoire** que l'entreprise
ne détient pas (agrément ARS, licence transport, inscription à l'ordre des
architectes…) ressort en `NO_GO` (score 0), quel que soit le reste du fit. Les
certifications seulement *appréciées* ne sont jamais éliminatoires.

### Périmètre

- **RC seul**, anonyme, poli (respect de `robots.txt`, throttle, retries).
  **Sans Playwright, sans Selenium, sans proxy, sans solveur de captcha.**
- Plateforme de documents supportée au lancement : **Atexo/PRADO** (PLACE,
  Maximilien, Megalis, AMPA, white-labels collectivités). Les avis routés vers
  une plateforme non supportée (ou nécessitant captcha/login/JS) sont **loggés et
  ignorés proprement** — jamais de contournement.
- Les RC scannés (~25 %) sont gérés par OCR (français) embarqué.
- Robuste par construction : un échec sur un avis n'interrompt pas le run ; le
  dataset partiel est préservé.

### Facturation

Monétisé en **Pay-Per-Event** : le démarrage est symbolique, et **un seul
événement de valeur (`tender-analyzed`) est facturé par analyse réussie** ayant
produit une décision dans le dataset. L'actor respecte la limite de facturation
configurée et s'arrête proprement une fois atteinte.

# Actor input Schema

## `consultationUrls` (type: `array`):

Liste d'URLs de consultation à analyser directement (court-circuite le feed BOAMP). Ex : https://marches.maximilien.fr/entreprise/consultation/123?orgAcronyme=ab

## `boampFilters` (type: `object`):

Filtres pour le feed BOAMP si aucune URL n'est fournie : keywords, cpv (liste), departement (liste), date\_min, date\_max, type\_avis.

## `maxNotices` (type: `integer`):

Plafond de récupération depuis BOAMP avant analyse.

## `maxTenders` (type: `integer`):

Plafond d'analyses (1 analyse = 1 décision facturée). Laisser vide pour ne pas limiter.

## `companyProfile` (type: `object`):

Profil contre lequel scorer : cpv\_codes\[], certifications\_detenues\[], references\[], geographies\[] (départements/régions), capacite\_financiere\_max, taille\_equipe.

## `llmTier` (type: `string`):

Modèle d'extraction. 'haiku' (rapide/économique, défaut) ou 'sonnet' (plus fin).

## `anthropicApiKey` (type: `string`):

Clé API Anthropic (ANTHROPIC\_API\_KEY). Stockée de façon sécurisée. Peut aussi être fournie via variable d'environnement.

## Actor input object example

```json
{
  "consultationUrls": [],
  "boampFilters": {},
  "maxNotices": 100,
  "companyProfile": {
    "cpv_codes": [
      "45000000"
    ],
    "certifications_detenues": [
      "Qualibat"
    ],
    "references": [
      "Chantier de référence 1"
    ],
    "geographies": [
      "75",
      "Île-de-France"
    ],
    "capacite_financiere_max": 500000,
    "taille_equipe": 20
  },
  "llmTier": "haiku"
}
```

# Actor output Schema

## `verdicts` (type: `string`):

Toutes les décisions bid/no-bid produites, au format JSON.

## `verdictsCsv` (type: `string`):

Les mêmes décisions en CSV (pratique pour la relecture en tableur).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companyProfile": {
        "cpv_codes": [
            "45000000"
        ],
        "certifications_detenues": [
            "Qualibat"
        ],
        "references": [
            "Chantier de référence 1"
        ],
        "geographies": [
            "75",
            "Île-de-France"
        ],
        "capacite_financiere_max": 500000,
        "taille_equipe": 20
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dr1ms/tender-triage").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "companyProfile": {
        "cpv_codes": ["45000000"],
        "certifications_detenues": ["Qualibat"],
        "references": ["Chantier de référence 1"],
        "geographies": [
            "75",
            "Île-de-France",
        ],
        "capacite_financiere_max": 500000,
        "taille_equipe": 20,
    } }

# Run the Actor and wait for it to finish
run = client.actor("dr1ms/tender-triage").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companyProfile": {
    "cpv_codes": [
      "45000000"
    ],
    "certifications_detenues": [
      "Qualibat"
    ],
    "references": [
      "Chantier de référence 1"
    ],
    "geographies": [
      "75",
      "Île-de-France"
    ],
    "capacite_financiere_max": 500000,
    "taille_equipe": 20
  }
}' |
apify call dr1ms/tender-triage --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=dr1ms/tender-triage",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Tender Triage — Bid/No-Bid pour marchés publics FR",
        "description": "À partir d'un avis BOAMP (ou d'une URL de consultation Atexo/PLACE), télécharge le Règlement de Consultation, en extrait un schéma décisionnel par LLM, et produit un score bid/no-bid contre un profil d'entreprise. Une entrée = une décision.",
        "version": "0.1",
        "x-build-id": "CDbpTmkv0ykWP6RsG"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/dr1ms~tender-triage/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-dr1ms-tender-triage",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/dr1ms~tender-triage/runs": {
            "post": {
                "operationId": "runs-sync-dr1ms-tender-triage",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/dr1ms~tender-triage/run-sync": {
            "post": {
                "operationId": "run-sync-dr1ms-tender-triage",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "companyProfile"
                ],
                "properties": {
                    "consultationUrls": {
                        "title": "URLs de consultation (Atexo/PLACE)",
                        "type": "array",
                        "description": "Liste d'URLs de consultation à analyser directement (court-circuite le feed BOAMP). Ex : https://marches.maximilien.fr/entreprise/consultation/123?orgAcronyme=ab",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "boampFilters": {
                        "title": "Filtres BOAMP",
                        "type": "object",
                        "description": "Filtres pour le feed BOAMP si aucune URL n'est fournie : keywords, cpv (liste), departement (liste), date_min, date_max, type_avis.",
                        "default": {}
                    },
                    "maxNotices": {
                        "title": "Nombre max d'avis à récupérer (BOAMP)",
                        "minimum": 1,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "Plafond de récupération depuis BOAMP avant analyse.",
                        "default": 100
                    },
                    "maxTenders": {
                        "title": "Nombre max d'analyses",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Plafond d'analyses (1 analyse = 1 décision facturée). Laisser vide pour ne pas limiter."
                    },
                    "companyProfile": {
                        "title": "Profil entreprise",
                        "type": "object",
                        "description": "Profil contre lequel scorer : cpv_codes[], certifications_detenues[], references[], geographies[] (départements/régions), capacite_financiere_max, taille_equipe."
                    },
                    "llmTier": {
                        "title": "Niveau LLM",
                        "enum": [
                            "haiku",
                            "sonnet"
                        ],
                        "type": "string",
                        "description": "Modèle d'extraction. 'haiku' (rapide/économique, défaut) ou 'sonnet' (plus fin).",
                        "default": "haiku"
                    },
                    "anthropicApiKey": {
                        "title": "Clé API Anthropic",
                        "type": "string",
                        "description": "Clé API Anthropic (ANTHROPIC_API_KEY). Stockée de façon sécurisée. Peut aussi être fournie via variable d'environnement."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
