# Connaissance du projet : brancher l'API Artemis

À coller dans un projet Lovable qui affiche les chiffres d'une officine : `Project settings` → `Knowledge` (10 000 caractères au plus ; cette notice en compte moins de 7 000).

## Ce qu'est l'API

L'API Artemis donne, en lecture seule, les chiffres d'UNE officine : ventes mois par mois, stock, marges, laboratoires, familles. Adresse : `https://artemis.sydera.ai/api/v1`. Contrat OpenAPI : `https://artemis.sydera.ai/api/v1/openapi.json`. La clé (`sk_art_…`) est créée par le titulaire dans Artemis, onglet API ; elle ne lit que son officine.

## Quel niveau de clé demander

Chaque clé a un niveau, choisi par le titulaire à sa création, qui ne se modifie pas :

- `catalogue` : produits, prix de vente, stock ;
- `ventes` : le catalogue, plus les ventes mois par mois, les palmarès, la saison, le stock dormant et la couverture, sans prix d'achat ni marge ;
- `complet` : tout, prix d'achat et marges compris.

Demandez une clé de niveau `ventes` : elle suffit à presque tous les tableaux de bord. Ne demandez `complet` que si l'application affiche vraiment des marges ou des prix d'achat, et `catalogue` si elle n'affiche que les produits, les prix de vente et le stock. Une route au-dessus du niveau de la clé répond 403 `niveau_insuffisant`, et un champ réservé à un niveau plus haut n'apparaît jamais dans la réponse : prévoyez qu'il manque.

## Règle absolue : appeler l'API depuis une fonction serveur, jamais depuis la page

La clé ne va jamais dans le navigateur.

- Ne jamais mettre la clé dans le code de l'application (React, variables `VITE_…`, localStorage) : tout visiteur pourrait la lire. L'API refuse d'ailleurs les appels venus d'un navigateur.
- Stocker la clé comme secret du projet, nommé `ARTEMIS_API_KEY` : `More` → `Cloud` → `Secrets` → `Add secret`, ou le formulaire `Add secret` que Lovable propose. Jamais la clé dans la conversation.
- Appeler l'API depuis une fonction serveur (Edge Function de Lovable Cloud, ou fonction serveur d'un projet TanStack Start), jamais depuis la page, et ne renvoyer à l'application que ce qu'elle affiche.
- Protéger cette fonction : seuls les utilisateurs connectés de l'application l'appellent (le modèle ci-dessous le vérifie). Si l'application est publique, ne renvoyer que des chiffres qu'on accepte de montrer à tous.

## Fonction serveur type (Edge Function de Lovable Cloud)

```ts
// supabase/functions/artemis/index.ts
// Relais vers l'API Artemis : la clé reste ici, jamais dans le navigateur.
import { createClient } from "npm:@supabase/supabase-js@2";

const ARTEMIS = "https://artemis.sydera.ai/api/v1";
const ROUTES = new Set(["officine", "recherche", "fiche", "bilan", "top-ventes", "palmares", "laboratoires", "gamme",
  "ventes-du-mois", "stock-dormant", "couverture", "liste", "produits", "ventes", "stock"]);

Deno.serve(async (req) => {
  const cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, apikey, content-type, x-client-info" };
  if (req.method === "OPTIONS") return new Response("ok", { headers: cors });
  // Seul un utilisateur CONNECTÉ de l'application lit les chiffres : la clé
  // publique de l'application (anon) ne suffit pas.
  const sb = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_ANON_KEY")!, {
    global: { headers: { Authorization: req.headers.get("Authorization") ?? "" } },
  });
  const { data: { user } } = await sb.auth.getUser();
  if (!user) return new Response(JSON.stringify({ erreur: "connexion" }), { status: 401, headers: cors });
  const { route, params } = await req.json() as { route: string; params?: Record<string, string> };
  if (!ROUTES.has(route)) return new Response(JSON.stringify({ erreur: "route" }), { status: 400, headers: cors });
  const url = new URL(`${ARTEMIS}/${route}`);
  for (const [k, v] of Object.entries(params ?? {})) url.searchParams.set(k, v);
  const r = await fetch(url, { headers: { Authorization: `Bearer ${Deno.env.get("ARTEMIS_API_KEY")}` } });
  return new Response(await r.text(), { status: r.status, headers: { ...cors, "Content-Type": "application/json" } });
});
```

Côté application : `supabase.functions.invoke("artemis", { body: { route: "top-ventes", params: { mois: "3", limite: "20" } } })`.

## Les routes

- Lectures, réponse `{ donnees: { … }, date_releve }` :
  - `officine` : identité, stock, qualité des données ; les douze derniers mois pleins dès le niveau `ventes` ;
  - `recherche?q=` : chercher un produit ;
  - `fiche?cip=` : détail d'un ou plusieurs produits ;
  - `bilan?mois=` : mois par mois, marge comprise (niveau `complet`) ;
  - `top-ventes`, `palmares?par=laboratoire|famille`, `laboratoires`, `gamme?laboratoire=` (niveau `ventes`) ;
  - `ventes-du-mois?mois=AAAA-MM` (niveau `ventes`) ;
  - `stock-dormant`, `couverture` (niveau `ventes`) ;
  - `liste?quoi=laboratoires|familles`.
- Exports paginés, réponse `{ donnees: [ … ], nombre, suivant, date_releve }` : `produits`, `stock`, et `ventes` (niveau `ventes`). Repasser `suivant` dans `curseur` jusqu'à `null`.

Le détail des paramètres, et le niveau de chaque route et de chaque champ (`x-niveau`), sont dans le contrat OpenAPI.

## Afficher les chiffres honnêtement

- Toujours afficher la date du relevé (`date_releve`) près des chiffres.
- Afficher la `note` quand elle existe : elle dit ce qui compte (famille estimée, relevé ancien, produits mis à part).
- La marge (clé de niveau `complet`) est calculée sur le prix d'achat moyen pondéré, hors remises de fin d'année et accords avec les laboratoires : l'écrire « marge par produit », pas « marge de l'officine ».
- Un champ absent ou `null` veut dire inconnu, pas zéro. Un produit sans prix d'achat connu n'a pas de marge.
- Le mois en cours est partiel.

## Erreurs et limites

- Erreurs : `{ erreur, message }`, message en français, affichable tel quel.
- 401 : clé absente ou coupée. 403 : accès en pause, ou clé d'un niveau trop bas pour la route (`niveau_insuffisant`, le message nomme le niveau requis). 409 : chiffres pas encore arrivés. 429 : attendre `Retry-After` secondes. 503 : réessayer plus tard.
- 60 lectures par minute et 10 000 par jour par clé : mettre en cache côté serveur ce qui change peu (le relevé est quotidien).

## Ce que l'API ne fait pas

- Pas de recommandation de prix et pas de données de marché (aucun prix d'une autre pharmacie ou d'un site en ligne) : pour cela, PriceCraft.
- Aucune écriture : l'API ne modifie rien, ni prix, ni stock.
- Pas d'identifiant d'officine à passer : l'officine vient de la clé.
- Aucune donnée patient n'existe dans l'API.
