diff --git a/.agents/skills/domain-modeling/ADR-FORMAT.md b/.agents/skills/domain-modeling/ADR-FORMAT.md new file mode 100644 index 0000000..da7e78e --- /dev/null +++ b/.agents/skills/domain-modeling/ADR-FORMAT.md @@ -0,0 +1,47 @@ +# ADR Format + +ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. + +Create the `docs/adr/` directory lazily — only when the first ADR is needed. + +## Template + +```md +# {Short title of the decision} + +{1-3 sentences: what's the context, what did we decide, and why.} +``` + +That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most ADRs won't need them. + +- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited +- **Considered Options** — only when the rejected alternatives are worth remembering +- **Consequences** — only when non-obvious downstream effects need to be called out + +## Numbering + +Scan `docs/adr/` for the highest existing number and increment by one. + +## When to offer an ADR + +All three of these must be true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." + +### What qualifies + +- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." +- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." +- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. +- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. +- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. +- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." +- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/.agents/skills/domain-modeling/CONTEXT-FORMAT.md b/.agents/skills/domain-modeling/CONTEXT-FORMAT.md new file mode 100644 index 0000000..eaf2a18 --- /dev/null +++ b/.agents/skills/domain-modeling/CONTEXT-FORMAT.md @@ -0,0 +1,60 @@ +# CONTEXT.md Format + +## Structure + +```md +# {Context Name} + +{One or two sentence description of what this context is and why it exists.} + +## Language + +**Order**: +{A one or two sentence description of the term} +_Avoid_: Purchase, transaction + +**Invoice**: +A request for payment sent to a customer after delivery. +_Avoid_: Bill, payment request + +**Customer**: +A person or organization that places orders. +_Avoid_: Client, buyer, account +``` + +## Rules + +- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`. +- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does. +- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. +- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. + +## Single vs multi-context repos + +**Single context (most repos):** One `CONTEXT.md` at the repo root. + +**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other: + +```md +# Context Map + +## Contexts + +- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders +- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments +- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping + +## Relationships + +- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking +- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices +- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money` +``` + +The skill infers which structure applies: + +- If `CONTEXT-MAP.md` exists, read it to find contexts +- If only a root `CONTEXT.md` exists, single context +- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved + +When multiple contexts exist, infer which one the current topic relates to. If unclear, ask. diff --git a/.agents/skills/domain-modeling/SKILL.md b/.agents/skills/domain-modeling/SKILL.md new file mode 100644 index 0000000..d0f7e1a --- /dev/null +++ b/.agents/skills/domain-modeling/SKILL.md @@ -0,0 +1,74 @@ +--- +name: domain-modeling +description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model. +--- + +# Domain Modeling + +Actively build and sharpen the project's domain model as you design. This is the *active* discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.) + +## File structure + +Most repos have a single context: + +``` +/ +├── CONTEXT.md +├── docs/ +│ └── adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives: + +``` +/ +├── CONTEXT-MAP.md +├── docs/ +│ └── adr/ ← system-wide decisions +├── src/ +│ ├── ordering/ +│ │ ├── CONTEXT.md +│ │ └── docs/adr/ ← context-specific decisions +│ └── billing/ +│ ├── CONTEXT.md +│ └── docs/adr/ +``` + +Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed. + +## During the session + +### Challenge against the glossary + +When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" + +### Sharpen fuzzy language + +When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." + +### Discuss concrete scenarios + +When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. + +### Cross-reference with code + +When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" + +### Update CONTEXT.md inline + +When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). + +`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. + +### Offer ADRs sparingly + +Only offer to create an ADR when all three are true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will wonder "why did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). diff --git a/.agents/skills/domain-modeling/agents/openai.yaml b/.agents/skills/domain-modeling/agents/openai.yaml new file mode 100644 index 0000000..7f1522d --- /dev/null +++ b/.agents/skills/domain-modeling/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Domain Modeling" + short_description: "Build and sharpen a domain model" diff --git a/.agents/skills/grill-me/SKILL.md b/.agents/skills/grill-me/SKILL.md new file mode 100644 index 0000000..9470cfc --- /dev/null +++ b/.agents/skills/grill-me/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-me +description: A relentless interview to sharpen a plan or design. +disable-model-invocation: true +--- + +Run a `/grilling` session. diff --git a/.agents/skills/grill-me/agents/openai.yaml b/.agents/skills/grill-me/agents/openai.yaml new file mode 100644 index 0000000..4d6fb0c --- /dev/null +++ b/.agents/skills/grill-me/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Grill Me" + short_description: "Sharpen a plan through interview" +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/grill-with-docs/SKILL.md b/.agents/skills/grill-with-docs/SKILL.md new file mode 100644 index 0000000..bed05d2 --- /dev/null +++ b/.agents/skills/grill-with-docs/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-with-docs +description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go. +disable-model-invocation: true +--- + +Run a `/grilling` session, using the `/domain-modeling` skill. diff --git a/.agents/skills/grill-with-docs/agents/openai.yaml b/.agents/skills/grill-with-docs/agents/openai.yaml new file mode 100644 index 0000000..5dbe278 --- /dev/null +++ b/.agents/skills/grill-with-docs/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Grill with Docs" + short_description: "Grill a design and write its docs" +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/grilling/SKILL.md b/.agents/skills/grilling/SKILL.md new file mode 100644 index 0000000..95bd01e --- /dev/null +++ b/.agents/skills/grilling/SKILL.md @@ -0,0 +1,22 @@ +--- +name: grilling +description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases. +--- + +Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it. + +Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round. + +Each question should be formatted like so: + +``` +❓ **Q1** - ****: + +➡️ +``` + +Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one. + +Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it — don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report — ask the rest of the frontier now. The _decisions_ are the user's — put each to them and wait. + +The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding. diff --git a/.agents/skills/grilling/agents/openai.yaml b/.agents/skills/grilling/agents/openai.yaml new file mode 100644 index 0000000..ddbdb96 --- /dev/null +++ b/.agents/skills/grilling/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Grilling" + short_description: "Stress-test thinking a round of questions at a time" diff --git a/.agents/workflows/feature.md b/.agents/workflows/feature.md new file mode 100644 index 0000000..2f25bb3 --- /dev/null +++ b/.agents/workflows/feature.md @@ -0,0 +1,39 @@ +# Feature workflow + +Entry test: if you can already name every file the change touches, and none of them are imported by more than the one feature you're changing, go straight to step 4. Otherwise — or if you're not sure — start at step 1. That's the same bar `CLAUDE.md` sets ("non trivial ou ambigu"), made checkable: naming the files is easy to fake from memory, so the test is really "did you grep to confirm it," not "does it feel obvious." + +## 1. Plan + +Hand the request to `architect`. It explores the real codebase before proposing anything, reaches for the `domain-modeling` skill itself when domain vocabulary is involved (see `CONTEXT.md`), and stops at a plan — no code. It states defaults for easily-reversible choices and only raises blocking questions for the ones that are hard to reverse or genuinely ambiguous. + +## 2. Challenge + +Run `grill-me` against the plan. Resolve every blocking question it raises before moving on. + +## 3. Validate with the human + +Per this repo's own process rule (`CLAUDE.md` → "Process avant tout travail de code non trivial ou ambigu"), get explicit confirmation of the plan — including the precise file list — before writing code. `architect` treats a decision as settled only once you've confirmed it in conversation, not by default; this step is where that confirmation actually happens. + +## 4. Implement + +Implement the approved plan incrementally. Read a file's current state before editing it — never assume session memory is up to date (`CLAUDE.md`). + +## 5. Test strategy + +Ask `test-strategist` for a prioritized list of what's actually worth testing for this diff — anchored on this repo's own conventions and named scientific invariants, not generic advice. Write the tests it identifies as valuable; skip the ones it explicitly says aren't. + +## 6. Quality review + +Run `quality-reviewer` against the final diff. It checks against `CLAUDE.md`'s documented conventions specifically (component style, naming, import order, domain purity, `data-testid` patterns) — it doesn't hunt bugs. Also run `/code-review` when the diff touches code shared across features, external I/O, or anything else hard to reverse; a single-feature change with no cross-cutting reach is already covered by `quality-reviewer` + `test-strategist` alone. + +## 7. Validate + +Run: +- `pnpm check` +- `pnpm lint` +- `pnpm test:run` +- `pnpm build` + +## 8. Record the decision, if it earned one + +If `architect` or `quality-reviewer` flagged the change as ADR-worthy (hard to reverse, surprising without context, a real trade-off), write the ADR now that it's actually settled — via the `domain-modeling` skill, following `ADR-FORMAT.md`. Don't write it earlier: an ADR records a decision, not the deliberation that led to it. diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..c940ab8 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,181 @@ +## Projet + +**EuroMillions Geometry Lab** (izeetok) — SPA React qui traite les grilles/tirages EuroMillions +comme des objets mathématiques (features, géométrie, PCA/UMAP, clustering, backtesting), sans +jamais suggérer une capacité prédictive. Roadmap en 4 versions, voir `devdx/specs_v{1..4}.md` : +V1 analyse une grille, V2 teste une hypothèse (backtest), V3 construit l'univers mathématique +(embeddings/discovery), V4 permet de le visualiser en 3D (`/eurospace`). + +Décisions d'architecture actées, voir `docs/adr/` : +- **ADR-0001** : aucun backend — SPA 100% client-side (déployée sur Vercel), dataset de tirages en + CSV committé (source FDJ), état utilisateur en `localStorage`. +- **ADR-0002** : on construit d'abord un spike vertical (`Grid → FeatureExtractor → + GeometryDescriptor → PCA → coordonnées shape SpatialEmbedding → point cloud three.js`) avant de + bâtir V1 à V4 en entier — ça valide le contrat `SpatialEmbedding` (V3) que V4 consomme, avant + d'investir dans les pages complètes. + +Vocabulaire du domaine (Grid, Draw, FeatureVector, GeometryDescriptor, SpatialEmbedding, +DiscoveryModel, Family, Strategy, Experiment, TemporalWindow, les 4 scores...) : voir `CONTEXT.md` +à la racine. Le principe non-négociable répété dans chaque spec : toutes les combinaisons ont la +même probabilité théorique ; l'app analyse des structures historiques, elle ne prédit rien. + +## Périmètre + +Repo à plat (pas de monorepo `apps/`) : je lis et j'écris dans tout le repo — `src/`, `public/`, +`docs/`, racine. `devdx/` (specs sources) reste ignoré par git mais en lecture libre pour le +contexte. `.claude/` et `.agents/` sont versionnés (outillage agentique partagé avec l'équipe : +`CLAUDE.md`, `.claude/agents/`, `.agents/workflows/`, skills). + +Process avant tout travail de code non trivial ou ambigu : +1. Je propose une approche/plan. +2. Je liste précisément les **fichiers à modifier/créer** et **ce que je vais y faire**. +3. Une fois le plan/la tâche approuvé (y compris via la todo-list de session), j'exécute sans + redemander fichier par fichier pour ce qui est déjà dans le plan. +4. Je fais un **récap des fichiers touchés et des modifications réellement apportées**. + +En cas d'ambiguïté dans les specs (V1-V4) ou dans une ADR : je pose la question, je ne fais pas +d'hypothèse implicite. + +### Commandes + +- **Libres sans demander** : tout ce qui **vérifie ou installe en local** — `pnpm lint`, `pnpm + test`, `pnpm build`, `pnpm check` (tsc), `pnpm exec vitest`, `pnpm dev` (serveur dev), `pnpm + add`/`remove` (dépendances), `pnpm exec dx-flow ...`. Git en local : `status`, `diff`, `log`, + `add`, `commit`. +- **Autorisation requise avant** : `git push` (et *a fortiori* tout `--force`), suppression de + branche, réécriture d'historique (`rebase`, `reset --hard`, `amend`), toute action GitHub + (PR, issue, release), et toute commande destructive hors dépôt. + +## Stack + +Stack V1 (specs), déjà en place ou à installer au fil des tâches : + +| package | rôle | +|---|---| +| `react` / `react-dom` `^19` | Framework UI | +| `typescript` `~6.0` | Typage statique | +| `vite` `^8` | Build tool | +| `react-router` | Routing (V1 : `/evaluation`, `/draws`, `/geometry` ; V2 ajoute `/laboratory` ; V3 `/discovery` ; V4 `/eurospace`) | +| `@tanstack/react-query` | Fetch / cache (CSV, futur adapter API) | +| `zod` | Validation (`Grid`, formulaires) | +| SCSS / CSS Modules | Styles | +| `recharts` | Visualisations 2D ; `d3` seulement pour le sur-mesure avancé | +| `vitest` + Testing Library + Playwright | Tests (unit + E2E) | +| `three` / `@react-three/fiber` / `@react-three/drei` | V3 (point cloud spike) puis V4 (`/eurospace`) | +| `pnpm` | Package manager | +| `oxlint` | Linter (voir plus bas — remplace Biome) | +| `@keyobs/dx-flow` (husky + commitlint + lint-staged) | Hooks Git, commits conventionnels, scripts de release | + +Pas d'i18n (i18next) ni de Storybook : aucune des specs V1-V4 n'en a besoin pour l'instant. À +reconsidérer seulement si un besoin réel apparaît. + +## Structure + +Architecture V1 (le domaine mathématique ne dépend jamais de React ni d'une lib de rendu) : + +``` +src/ + app/ + pages/ + evaluation/ + draws/ + geometry/ + domain/ + grid/ + draw/ + features/ + geometry/ + scoring/ + application/ + infrastructure/ + repositories/ + csv/ + api/ + shared/ +``` + +V2 ajoute `pages/laboratory/` + le moteur de backtest dans `domain`/`application`. V3 ajoute +`pages/discovery/` + `domain/discovery` (embeddings, clustering). V4 ajoute `features/eurospace/` +et `pages/eurospace/` (voir `devdx/specs_v4.md#architecture-proposée` pour le détail). + +**Alias d'import** : pas encore configurés (imports relatifs pour l'instant). La section +"Ordre des imports" ci-dessous décrit la cible (`@models/`, `@api/`, `@utils/`, `@hooks/`, +`@providers/`, `@components/`) à mettre en place via `tsconfig`/`vite.config` quand le nombre de +fichiers le justifiera — ne pas inventer ces alias avant qu'ils existent réellement. + +## Style de dev + +- Composants fonctionnels uniquement, définis en arrow function + export par défaut, jamais + `export function XxxComponent() {...}` : + ```ts + const XxxComponent = (props: XxxComponentProps) => { ... }; + export default XxxComponent; + ``` + **Providers** : Context et Provider séparés dans deux fichiers (`XxxContext.tsx` = `createContext` + + hook `useXxx`, tout en export nommé ; `XxxProvider.tsx` = le composant seul, arrow + export par + défaut comme les autres composants) — évite de mélanger export par défaut et export nommé dans un + même fichier. Ex : `ApiContext.tsx`/`ApiProvider.tsx`. +- Props en interface nommée (`interface XxxProps`) +- Pas de `any`, pas de `unknown` sans narrowing immédiat +- Imports React : hooks nommés individuellement — `import { useState, useEffect } from 'react'` +- Imports terminés par un point-virgule (`;`) +- Ne pas utiliser `FormEvent`/`FormEventHandler` (dépréciés dans les types React) : utiliser + `SubmitEvent`, `ChangeEvent`, ou `SyntheticEvent` selon le cas +- Éviter l'attribut `autoFocus` (a11y) +- Commentaires dans le code : toujours en anglais +- Pas d'abstraction prématurée — 3 lignes similaires ne justifient pas un helper +- `data-testid` sur tout élément interactif ou observable (boutons, lignes de table, états vides...) + — pattern `{prefix}-{role}` pour les éléments répétés +- **Fichiers : toujours `Read` l'état courant avant modification** — ne jamais supposer que la + mémoire de session est à jour. `Edit` ciblé, jamais `Write` sur un fichier existant. +- Context React uniquement pour les valeurs partagées quasi-statiques ; `useState`/`useReducer` + pour l'état local +- Le domaine mathématique (`domain/`) reste pur TypeScript, testable sans React et sans lib de + rendu (three.js compris) — c'est la règle centrale des ADR/specs + +### Ordre des imports +``` +1. CSS / SCSS / SASS +2. Libs (react, react-router, @tanstack/react-query, …) +3. App — dans cet ordre : + 3a. types / models (@models/… ou src/domain relatif tant que l'alias n'existe pas) + 3b. api (@api/… ou src/infrastructure relatif) + 3c. utils (@utils/…) + 3d. hooks (@hooks/… ou hook colocalisé de la feature) + 3e. providers (@providers/…) + 3f. composants partagés (@components/…) + 3g. composants colocation (même dossier feature, imports relatifs ./…) +``` + +### Nommage & fichiers +- Composants React `.tsx` : PascalCase (`Gates.tsx`) +- Hooks `.ts` : camelCase (`useGates.ts`) +- Fichiers non-hook `.ts`/`.scss` : camelCase +- Fichiers test : camelCase (`Gates.test.tsx`) + +### Tests +- **Vitest** — objectif **80% de couverture**, en particulier sur `domain/` (logique scientifique) +- Composants : au minimum un test de rendu sans crash +- **Privilégier des helpers purs testables sans React** quand la logique s'y prête : une logique + extraite d'un composant est une logique qui reste couverte +- Playwright pour l'E2E (parcours saisie → évaluation → géométrie, etc., voir critères d'acceptation + de chaque spec) +- Invariants scientifiques toujours couverts : invariance à l'ordre d'une `Grid`, `distance(x,x)=0` + et symétrie, absence de fuite temporelle dans les backtests, déterminisme du seed + +## Git workflow + +- Hébergé sur GitHub. +- **Branches** : `main` protégée (aucun commit/push direct — bloqué par le hook `pre-commit` de + `dx-flow`), `develop` = branche de travail courante. Pas de branche par tâche par défaut ; on + crée une branche dédiée seulement si explicitement demandé. +- **Commits** : autorisés — un commit conventionnel par tâche terminée, sur `develop`, sauf + instruction contraire. Format imposé par `commitlint.config.mjs` : + `type(scope): subject` (**scope obligatoire**). Types acceptés (insensibles à la casse) : `feat`, + `fix`, `doc`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `core`, `revert`, + `merge`, `config`, `clean`. +- **Push** : jamais sans autorisation explicite, même sur `develop`. +- Hooks `dx-flow` actifs : `pre-commit` (branche protégée, garde anti-`.env`, `lint-staged`, + `tsc --noEmit` sur les fichiers staged), `commit-msg` (commitlint), `pre-push` (`lint:fix` puis + `test:run` — bloque le push vers `develop` si les tests échouent). + diff --git a/.claude/agents/architect.md b/.claude/agents/architect.md new file mode 100644 index 0000000..bf06a7b --- /dev/null +++ b/.claude/agents/architect.md @@ -0,0 +1,15 @@ +--- +name: architect +description: Turns a feature request, bug, or architecture question into an implementation plan for this repo. Explores the current structure and conventions before proposing anything, consults the domain-modeling skill when domain vocabulary is involved, and stops at a plan — no code, no edits. Use before any non-trivial or ambiguous change. +tools: Read, Grep, Glob, Bash, Skill, TaskCreate, TaskUpdate, TaskList +--- + +You are the architect for this repo. Given a request, produce a plan — never write or edit code, never use Edit/Write. + +1. **Explore before proposing, proportionally to the request.** Read the actual files involved, don't reason from memory of the codebase. If you're about to claim "X is used by Y", grep for it and check — don't assume from naming. Calibrate the depth to the stakes: a one-file fix can be verified quickly; a structural question requires tracing real imports before naming a destination for anything. +2. **Read `CLAUDE.md` and `docs/adr/` first** for current structure, conventions, and decisions already made. Never hardcode a folder layout in your own reasoning — the live source of truth is those files, not this prompt. +3. **Use the `domain-modeling` skill** when the request touches domain vocabulary (see `CONTEXT.md`), introduces a new term, or could conflict with an existing ADR. +4. **Surface ambiguity, calibrated.** When a reasonable, easily-reversible default exists, state it explicitly in your plan and proceed on that basis, flagged as a default — don't resolve it silently, but don't block on it either. Reserve blocking questions for choices that are hard to reverse or for readings that are genuinely equally plausible. +5. **Output a plan**: the precise list of files to create/modify and what changes in each — matching this repo's own process rule (`CLAUDE.md` → "je liste précisément les fichiers à modifier/créer"). No code. +6. **Flag ADR-worthy decisions, don't write them.** If a choice is hard to reverse, surprising without context, and a real trade-off (the domain-modeling skill's 3-part test), say so explicitly. Treat a decision as settled only once the user has explicitly confirmed it in the conversation — not merely because nobody has objected. +7. **Default to showing your reasoning and a concrete recommendation, not silence.** An open question should come with your best-guess answer, unless voicing that guess would itself be presumptuous (see point 4). diff --git a/.claude/agents/deploy-troubleshooter.md b/.claude/agents/deploy-troubleshooter.md new file mode 100644 index 0000000..9ae7642 --- /dev/null +++ b/.claude/agents/deploy-troubleshooter.md @@ -0,0 +1,14 @@ +--- +name: deploy-troubleshooter +description: Diagnoses a failed CI run or a broken deployment for this repo — Vercel (production, on main) or GitHub Pages (staging, on develop). Reads the actual failure output before proposing anything, tells apart a gate failure (check/lint/test) from a build failure from a platform-side issue, and stops at a diagnosis — no code, no edits, no CLI deploys. +tools: Read, Grep, Glob, Bash, Skill +--- + +You are the deploy troubleshooter for this repo. Given a failed CI run or a broken deployment, find the actual cause — never write or edit code, never use Edit/Write, never run a deploy yourself (`main` deploys to Vercel through its native Git integration; `develop` deploys to GitHub Pages through `deploy-develop-pages.yml` — neither goes through a CLI you'd invoke here). + +1. **Start from the real failure output, not the workflow file's intent.** Use `gh run view --log-failed` (or the specific run/job the user points you to) to read what actually happened before reasoning about what should have happened. +2. **Name which stage failed before proposing a fix**: `pnpm install` itself (this repo depends on `@keyobs/dx-flow` from GitHub Packages — even public packages there require auth, unlike npmjs; a bare `401 Unauthorized` at install time means either the workflow's `permissions: packages: read` is missing, or `dx-flow`'s "Manage Actions access" no longer grants this repo — check that before assuming a lockfile/code problem), the CI gate (`pnpm check`/`lint`/`test:run` in `test.yml`), the build (`pnpm build`, with `VITE_BASE_PATH=/izeetok/` set only for the GitHub Pages build), the GitHub Pages deploy step (`actions/deploy-pages`, visible in the run log), or Vercel's deploy/runtime (only visible in the Vercel dashboard, since this repo has no CLI token configured here) — each has a different fix path, and guessing the wrong one wastes a cycle. +3. **For a Vercel-side failure**, you can't fetch its logs directly without an authenticated CLI — say so, and point to exactly what to check in the Vercel dashboard (the specific deployment, the build or function logs) rather than guessing at the cause from the app code alone. A GitHub Pages failure, by contrast, is fully visible in the Actions run log — use that directly. +4. **Ground your diagnosis in the exact command and its exact output** — quote the failing line, not a paraphrase. +5. **Give the command to reproduce the failure locally** whenever the failing stage runs a command that exists in `package.json` (`pnpm check`, `pnpm lint`, `pnpm test:run`, `pnpm build`) — that's almost always faster to iterate on than re-running CI. +6. **Default to a likely root cause and a concrete next step**, not just a list of possibilities — unless the log genuinely supports more than one equally plausible cause, in which case say so and name what would distinguish them. diff --git a/.claude/agents/quality-reviewer.md b/.claude/agents/quality-reviewer.md new file mode 100644 index 0000000..fe909e7 --- /dev/null +++ b/.claude/agents/quality-reviewer.md @@ -0,0 +1,14 @@ +--- +name: quality-reviewer +description: Reviews a diff against this repo's own documented conventions in CLAUDE.md (component style, file naming, import order, domain purity, data-testid patterns, premature-abstraction avoidance) — not generic bug-hunting, that's what /code-review is for. Reports findings via ReportFindings, ranked by how directly they violate a stated convention. Use after implementation, before it's considered done. +tools: Read, Grep, Glob, Bash, Skill, ReportFindings +--- + +You are the quality reviewer for this repo. Given a diff (or a set of files), check it against this repo's own stated conventions — not general code quality, that's `/code-review`'s job. + +1. **Read `CLAUDE.md`'s "Style de dev" section first**, every time — conventions can change, don't rely on memory of a previous review. +2. **Check the diff file by file against the concrete rules**: arrow-function components with default export, named `XxxProps` interfaces, no `any`/un-narrowed `unknown`, individually-named React hook imports, semicolon-terminated imports, `SubmitEvent`/`ChangeEvent`/`SyntheticEvent` instead of the deprecated `FormEvent*`, no `autoFocus`, English-only comments, PascalCase components / camelCase hooks and other files, `data-testid="{prefix}-{role}"` on interactive/observable elements, the documented import order. +3. **Check structural rules that only make sense with context**: does new domain/analysis logic stay free of React and rendering imports; does a new Context/Provider pair follow the two-file split; is a new abstraction justified by real reuse (three similar lines don't earn a helper) rather than introduced pre-emptively. +4. **When a rule doesn't apply cleanly** (the convention is ambiguous for this specific case, or two documented rules pull in different directions), say so explicitly with your best reading rather than silently picking one — flag it the same way `architect` would. +5. **Report via `ReportFindings`**, ranked most-clearly-a-convention-violation first. Anchor every finding to the specific `CLAUDE.md` rule it violates — a finding with no citable rule behind it belongs in `/code-review`, not here. +6. **Say when there's nothing to report.** A clean diff against these conventions is a valid, useful outcome — don't manufacture findings to seem thorough. diff --git a/.claude/agents/release-manager.md b/.claude/agents/release-manager.md new file mode 100644 index 0000000..62b8496 --- /dev/null +++ b/.claude/agents/release-manager.md @@ -0,0 +1,15 @@ +--- +name: release-manager +description: Drafts CHANGELOG.md entries from conventional commits since the last release and recommends which version bump (patch/minor/major) they warrant. Never runs the actual release command (pnpm release:*) itself - that stays a human-triggered action once the draft is approved. +tools: Read, Grep, Glob, Bash, Edit, Write +--- + +You are the release manager for this repo. Given a request to prepare a release, draft the changelog and a version recommendation - never run `pnpm release:*`, `npm version`, or `git tag` yourself. + +1. **Gather commits since the last tag** (`git tag --sort=-creatordate` then `git log ..HEAD`; if no tag exists, use the full history). Read `commitlint.config.mjs`'s type list each time rather than assuming it's unchanged - it's the source of truth for what a commit's type means here. +2. **Categorize by type, not by guesswork.** Group commits under their actual conventional-commit type; skip anything that isn't user-facing (`chore`, `ci`, `test`, `build`) unless it's directly relevant context for a `feat`/`fix` in the same release. +3. **Recommend a semver bump with reasoning**: any `feat` → at least minor; `fix`/`perf`/`refactor`/etc. only → patch; a `!` after type/scope or a `BREAKING CHANGE:` footer → major. State which commits drove the recommendation, not just the number. +4. **Draft `CHANGELOG.md`** (create it, Keep-a-Changelog-styled, at the repo root if it doesn't exist yet - mirrors how `@keyobs/dx-flow` itself keeps one). Write it in English, matching this repo's commit message and README language. One line per entry - name the change, not every parameter/count/sub-detail behind it; save the depth for someone who opens the diff. Rephrase commit subjects for a reader who wasn't there, grouped by type, under the proposed version and today's date. +5. **Say so when a release isn't warranted** - if everything since the last tag is `chore`/`ci`/`test`, say that plainly instead of manufacturing a changelog entry to look useful. +6. **Verify the write before reporting it.** After calling Write/Edit on `CHANGELOG.md`, read it back and confirm the content is actually there - report only what you just confirmed on disk, never what you composed in your own reasoning but didn't verify was saved. +7. **Stop at the draft.** Once `CHANGELOG.md` is written and the bump is recommended, tell the user exactly which `pnpm release:*` command matches your recommendation - don't run it. diff --git a/.claude/agents/test-strategist.md b/.claude/agents/test-strategist.md new file mode 100644 index 0000000..f1684c3 --- /dev/null +++ b/.claude/agents/test-strategist.md @@ -0,0 +1,15 @@ +--- +name: test-strategist +description: Given a diff, a plan, or a piece of domain logic, identifies which tests are actually worth writing for this repo — and which aren't. Anchors on this repo's own testing conventions rather than generic test advice. Produces a prioritized list, not test files — no code, no edits. Use after a plan is implemented or when reviewing test coverage for existing logic. +tools: Read, Grep, Glob, Bash, Skill, TaskCreate, TaskUpdate, TaskList +--- + +You are the test strategist for this repo. Given a diff, a plan, or existing code, identify which tests matter — never write test files yourself, never use Edit/Write. + +1. **Read the actual diff/files, proportionally to their reach.** A small pure-function change needs a quick check; a change to `analysis/` (the pure scientific core) needs you to trace what invariants it touches before recommending anything. +2. **Ground every recommendation in this repo's own conventions**, not generic testing advice — read `CLAUDE.md`'s "Tests" section and the sibling `*.test.ts` files for the code under discussion before proposing new ones. Mirror the existing style (colocated, camelCase, Vitest + Testing Library). +3. **Prioritize the scientific invariants CLAUDE.md names explicitly** wherever a change touches the pure domain/analysis layer — order-invariance of a `Grid`, `distance(x, x) = 0` and symmetry, no temporal leakage in backtests, seed determinism. For each one that applies, check whether a test already covers it and name the precise gap (file + missing case), not a generic "add more tests." +4. **Favor pure, React-free helpers when logic is testable that way** — this repo already prefers extracting logic out of components for exactly that reason; point out when a component change could be tested more cheaply by extracting first. +5. **For UI/page changes**, recommend at minimum a render-without-crash test, and call out when a full user journey (e.g. saisie → évaluation → géométrie) needs Playwright coverage instead of — or in addition to — a unit test. +6. **Output a prioritized list**: missing tests worth writing, existing tests that are redundant/misplaced, and why — grounded in what actually changed, not a checklist run against every file in sight. +7. **Default to stating your judgment call and reasoning, not silence.** When "is this worth testing" is a genuine judgment call, say what you'd do and why, flagged as a recommendation — don't just list options. diff --git a/.claude/skills/domain-modeling b/.claude/skills/domain-modeling new file mode 120000 index 0000000..e672a60 --- /dev/null +++ b/.claude/skills/domain-modeling @@ -0,0 +1 @@ +../../.agents/skills/domain-modeling \ No newline at end of file diff --git a/.claude/skills/grill-me b/.claude/skills/grill-me new file mode 120000 index 0000000..eea91a8 --- /dev/null +++ b/.claude/skills/grill-me @@ -0,0 +1 @@ +../../.agents/skills/grill-me \ No newline at end of file diff --git a/.claude/skills/grill-with-docs b/.claude/skills/grill-with-docs new file mode 120000 index 0000000..f6cbb9c --- /dev/null +++ b/.claude/skills/grill-with-docs @@ -0,0 +1 @@ +../../.agents/skills/grill-with-docs \ No newline at end of file diff --git a/.claude/skills/grilling b/.claude/skills/grilling new file mode 120000 index 0000000..e712452 --- /dev/null +++ b/.claude/skills/grilling @@ -0,0 +1 @@ +../../.agents/skills/grilling \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..69c6fa8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,15 @@ +name: CI + +on: + pull_request: + +permissions: + contents: read + packages: read + +jobs: + tests: + name: Tests + uses: ./.github/workflows/test.yml + with: + ref: ${{ github.ref }} diff --git a/.github/workflows/deploy-develop-pages.yml b/.github/workflows/deploy-develop-pages.yml new file mode 100644 index 0000000..89857ff --- /dev/null +++ b/.github/workflows/deploy-develop-pages.yml @@ -0,0 +1,77 @@ +name: Deploy develop to GitHub Pages + +on: + push: + branches: [develop] + workflow_dispatch: + inputs: + ref: + description: "Branch or tag to deploy" + required: false + type: string + default: "" + +permissions: + contents: read + packages: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + tests: + name: Tests + uses: ./.github/workflows/test.yml + with: + ref: ${{ inputs.ref || github.ref }} + + build: + name: Build + needs: tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Setup pnpm + Node.js + uses: pnpm/setup@v2 + with: + version: 11.0.9 + runtime: node@24 + install: false + + - name: Configure private registry auth + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + env: + VITE_BASE_PATH: "/izeetok/" + run: pnpm build + + - name: SPA fallback for GitHub Pages (no server-side rewrites there) + run: cp dist/index.html dist/404.html + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: ./dist + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9f30806 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: Tests + +on: + workflow_call: + inputs: + ref: + description: "Ref (branch or tag) to test" + required: false + type: string + default: "" + +permissions: + contents: read + packages: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Setup pnpm + Node.js + uses: pnpm/setup@v2 + with: + version: 11.0.9 + runtime: node@24 + install: false + + - name: Configure private registry auth + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Type check + run: pnpm check + + - name: Lint + run: pnpm lint + + - name: Run tests + run: pnpm test:run diff --git a/.gitignore b/.gitignore index e051a40..83bc09b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,13 @@ dist-ssr devdx -# Skills installed via skills-lock.json (regenerated from the lockfile, like node_modules) -.agents/skills -.claude/skills \ No newline at end of file +# Husky internal hook runner (regenerated by the "prepare" script) +.husky/_ + +# Playwright +playwright-report +test-results +blob-report + +# Vercel CLI local link artifacts +.vercel \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..5b40392 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +echo "📝 Validating commit message..." + +npx --no -- commitlint --edit "$1" +status=$? + +if [ "$status" -eq 0 ]; then + echo "✅ Commit message follows conventional standards." +else + echo "❌ Invalid commit format!" + echo "💡 Use: (): tell why you changed that" + exit "$status" +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..666f8b9 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +run_exec() { + if [ -f pnpm-lock.yaml ]; then + pnpm exec "$@" + elif [ -f yarn.lock ]; then + yarn "$@" + elif [ -f bun.lockb ] || [ -f bun.lock ]; then + bunx "$@" + else + npx "$@" + fi +} + +echo "🚦 Pre-commit sanity check..." + +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then + echo "🔒 Don't commit to $branch." + exit 1 +fi + +staged_files="$(git diff --cached --name-only)" + +if printf "%s\n" "$staged_files" | grep -E '^\.env(\.|$)' >/dev/null; then + echo "❌ Error: You are attempting to commit a .env file!" + exit 1 +fi + +echo "🧹 Linting & formatting staged files..." +run_exec lint-staged +status=$? +if [ "$status" -ne 0 ]; then + exit "$status" +fi + +staged_ts="$(printf "%s\n" "$staged_files" | grep -E '\.(ts|tsx)$' || true)" +if [ -n "$staged_ts" ]; then + echo "🎓 Type checking..." + run_exec tsc -b --pretty false + status=$? + if [ "$status" -ne 0 ]; then + exit "$status" + fi +else + echo "ℹ️ No TS files staged for type checking." +fi + +echo "🎉 Pre-commit checks passed." diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..a1159a4 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +run_script() { + if [ -f pnpm-lock.yaml ]; then + pnpm run "$1" + elif [ -f yarn.lock ]; then + yarn "$1" + elif [ -f bun.lockb ] || [ -f bun.lock ]; then + bun run "$1" + else + npm run "$1" + fi +} + +has_script() { + node -e "const pkg = require('./package.json'); process.exit(pkg.scripts && pkg.scripts['$1'] ? 0 : 1)" +} + +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then + echo "🔒 Don't push to $branch." + exit 1 +fi + +echo "🧹 DX-FLOW: Running lint fix..." +if has_script lint:fix; then + run_script lint:fix + status=$? + if [ "$status" -ne 0 ]; then + exit "$status" + fi +fi + +echo "🧐 DX-FLOW: Running tests..." +if ! has_script test:run; then + exit 0 +fi + +run_script test:run +status=$? + +if [ "$branch" = "develop" ] && [ "$status" -ne 0 ]; then + echo "☠️ Tests failed. Push to develop aborted." + exit 1 +fi + +if [ "$status" -ne 0 ]; then + echo "⚠️ Tests failed, but allowing push on $branch." +fi diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..dad9f99 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@keyobs:registry=https://npm.pkg.github.com diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9989c94 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# Changelog + +All notable changes to this project are documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project follows conventional commits (see `commitlint.config.mjs`) for +release-note generation. + +## [Unreleased] + +## [0.1.0] - 2026-08-14 + +First release: grid evaluation, historical draws, geometry, backtesting lab, +structure discovery, plus the versioned agent tooling and CI/CD. + +### Added + +- Core domain model: `Grid`, `Draw`, feature extraction, geometry descriptors + and distance. +- Four evaluation scores (structure, originality, temporal, confidence) with + a reading classification. +- Grid variation generator (structurally-common, balanced, anti-share). +- `/evaluation`: grid input, scores, variations, already-drawn flags, + restores the last evaluated grid on reload. +- `/draws`: historical draws table with per-draw geometry and distance to the + previous draw. +- `/geometry`: scatter charts, decade histogram, gap map, nearest neighbors, + selectable reference grid. +- `/laboratory`: strategy builder, walk-forward backtester, baselines, Monte + Carlo comparison. +- `/discovery`: feature normalization, PCA, K-Means clustering, density/outlier + scoring. +- Sticky top navigation. +- CI (lint/type-check/test) gating `main` and `develop`; GitHub Pages + deployment for `develop` alongside Vercel for `main`. +- Versioned agent tooling: architect, test-strategist, quality-reviewer, + deploy-troubleshooter, release-manager, and a documented feature workflow. +- English README. + +### Changed + +- Reworked the UI with design tokens and a dark-first palette. + +### Fixed + +- Digit-entry inputs on `/evaluation` (auto-advance, live validity) instead + of native number spinners. +- Duplicate numbers/stars flagged red past their first occurrence. +- `/geometry`: plain-language descriptions, persistent reference banner, + highlighted reference point, readable gap map. +- CSV path now resolves under a sub-path deployment (was 404ing on GitHub + Pages). +- `check` script and pre-commit hook now actually type-check the project. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..0bb4dff --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,57 @@ +# EuroMillions Geometry Lab + +A data-visualization app that treats EuroMillions draws and user-chosen number grids as mathematical objects, analyzing their historical structure without claiming predictive power. + +## Language + +### Core objects + +**Grid**: +Five distinct numbers (1–50) plus two distinct stars (1–12) — either a historical draw's outcome or a user's proposed selection. +_Avoid_: Combination, ticket, selection + +**Draw**: +A `Grid` that actually occurred on a specific date, sourced from official results (CSV/API/manual entry). +_Avoid_: Result, tirage + +**FeatureVector**: +The numeric measurements computed from a `Grid` (sum, amplitude, decade distribution, gaps, parity, etc.) — the raw input to every downstream model. +_Avoid_: Stats, metrics + +**GeometryDescriptor**: +The geometric shape derived from a `Grid`'s `FeatureVector` — decade buckets, gaps, sum, range, odd/even counts, cluster size — independent of any particular rendering. +_Avoid_: Shape + +### Analysis pipeline + +**SpatialEmbedding**: +The single source of spatial truth for a `Draw`: its 3D coordinates (from PCA or UMAP), cluster membership, local density, outlier score, and nearest neighbors. Computed once by the `DiscoveryModel`; consumers such as EuroSpace never recalculate it. +_Avoid_: Projection, position + +**DiscoveryModel**: +The model that fits clusters, density, and stability measures across the full draw history to produce `SpatialEmbedding`s. +_Avoid_: Clustering engine + +**Family**: +A named, described group of structurally similar draws discovered by the `DiscoveryModel`, always paired with a stability rating — an unstable family is still described, but weighted lightly by scores. +_Avoid_: Cluster (used interchangeably in places, but "Family" is the user-facing term), group + +### Strategy & experimentation + +**Strategy**: +A configurable, seeded, reproducible rule set that proposes a `Grid` given a subset of history — the unit under test in a backtest. +_Avoid_: Model, algorithm + +**Experiment**: +One walk-forward backtest run of a `Strategy` against a dataset/model version, seed, and set of `TemporalWindow`s, producing an `ExperimentResult`. +_Avoid_: Backtest run, test + +**TemporalWindow**: +A historical lookback period (1/3/6/12/25/50/"all" years) used to evaluate temporal proximity or scope a `Strategy` run. None of these windows — including "6" — has privileged scientific status; they're just available lenses. +_Avoid_: Timeframe, period + +### Scores + +**Structure Score / Originality Score / Temporal Signal Score / Confidence Score**: +The four independent axes an evaluated `Grid` is scored on: historical structural proximity, likely-human-choice risk, validated temporal effects, and diagnostic stability. Always shown together as separate axes — never collapsed into one opaque verdict. +_Avoid_: Score (alone — ambiguous which axis) diff --git a/README.md b/README.md index d0d4eef..e039b73 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,75 @@ -# React + TypeScript + Vite +# EuroMillions Geometry Lab +Why predict when you could analyze? + +All combinations have the same theoretical probability: this app lets you analyze a combination against historical structures — it predicts nothing. + +Since every grid has the same odds of being drawn, this app instead lets you look at them through the lens of mathematical objects (features, geometry, PCA/UMAP, clustering, backtesting) — never suggesting any predictive power. + + +## Stack + +React 19 +TypeScript +Vite +react-router, TanStack Query, Zod +recharts, three.js/@react-three +pnpm. + +No backend — CSV dataset committed to the repo, user state in localStorage. + +## Getting started + +``` +pnpm install +pnpm dev +``` + +## Commands + +| Command | Role | +|---|---| +| `pnpm dev` | Dev server | +| `pnpm build` | Production build | +| `pnpm check` | Type-check (`tsc -b`) | +| `pnpm lint` / `pnpm lint:fix` | Lint (oxlint) | +| `pnpm test` / `pnpm test:run` | Unit tests (Vitest) | +| `pnpm test:e2e` | E2E tests (Playwright) | +| `pnpm release:patch/minor/major` | Version bump (dx-flow) | + +## Deployment + +`main` → Vercel (production, native Git integration). +`develop` → GitHub Pages: [keyobs.github.io/izeetok](https://keyobs.github.io/izeetok) + +## Architecture + +React SPA. +The mathematical domain stays pure TypeScript, decoupled from React: + +``` +src/ + app/ router, layout, config (queryClient...) + domain/ pure scientific logic — Grid, Draw, features, geometry, scoring, backtest... + application/ orchestration — repositories, variation generation, embeddings... + infrastructure/ data access — CSV, concrete repository implementations + pages/ one page per route (/evaluation, /geometry, /draws, /laboratory, /discovery) + components/ generic reusable components, no business logic + shared/ cross-cutting test tooling +``` + + +## Claude Code & agents + +The agentic tooling is versioned, not just used locally: + +- `.claude/CLAUDE.md` — conventions and working process for Claude Code on this repo. +- `.claude/agents/` — specialized subagents: + - `architect` — explores and plans a feature/change, doesn't write code + - `test-strategist` — identifies which tests are worth writing + - `quality-reviewer` — checks a diff against this repo's conventions + - `deploy-troubleshooter` — diagnoses a broken CI/deploy run + - `release-manager` — drafts the changelog and recommends the version bump +- `.agents/skills/` — shared skills (`domain-modeling`, `grilling`, `grill-me`, `grill-with-docs`). +- `.agents/workflows/feature.md` — chains plan → challenge → human validation → + implementation → tests → review → validation, for any non-trivial feature. diff --git a/commitlint.config.mjs b/commitlint.config.mjs new file mode 100644 index 0000000..a48896a --- /dev/null +++ b/commitlint.config.mjs @@ -0,0 +1,70 @@ +const headerPattern = /^(?:([a-zA-Z0-9-]+):\s)?(\w+)\(([^)]+)\):\s(.+)$/; + +export default { + extends: ["@commitlint/config-conventional"], + parserPreset: { + parserOpts: { + headerPattern, + headerCorrespondence: ["ticket", "type", "scope", "subject"], + }, + }, + plugins: [ + { + rules: { + "type-enum-insensitive": ({ type }, when, value) => { + if (!type) return [true]; + const isFound = value.some((val) => val.toLowerCase() === type.toLowerCase()); + return [ + when === "never" ? !isFound : isFound, + `type must be one of [${value.join(", ")}]`, + ]; + }, + }, + }, + ], + rules: { + "scope-empty": [2, "never"], + "type-case": [0], + "type-enum": [0], + "subject-case": [0], + "type-enum-insensitive": [ + 2, + "always", + [ + "feat", + "fix", + "doc", + "style", + "refactor", + "perf", + "test", + "build", + "ci", + "chore", + "core", + "revert", + "merge", + "config", + "clean" + ], + ], + }, +}; + +/* +- build — Build system or dependencies +- chore — Maintenance or tooling tasks +- ci — Continuous integration changes +- config — Configuration changes +- core — Application logic or behavior +- doc — Documentation changes +- feat — New feature +- fix — Bug fix +- merge — Merge branches +- perf — Performance improvements +- refactor — Code restructure, same behavior +- revert — Revert previous commit +- style — Formatting, no logic change +- test — Add or update tests +- clean — Code cleanup, no logic change +*/ diff --git a/docs/adr/0001-client-side-only-architecture.md b/docs/adr/0001-client-side-only-architecture.md new file mode 100644 index 0000000..84c7c8b --- /dev/null +++ b/docs/adr/0001-client-side-only-architecture.md @@ -0,0 +1,7 @@ +# Client-side-only architecture, no backend + +The app targets the author, friends, and recruiters viewing a portfolio piece — not a large public audience — with a dataset of roughly 2,000 historical draws. We decided against any backend: the app is a static SPA (deployed on Vercel) where all compute (CSV parsing, feature extraction, PCA/UMAP, clustering, backtesting) runs client-side, the draw dataset is a manually-refreshed CSV committed to the repo rather than fetched from a live API, and any per-user state (saved grids) lives in `localStorage` only, with no accounts. + +This keeps hosting and ops at zero, avoids redistributing FDJ's draw data through a public API of our own (its licensing terms are ambiguous), and doubles as a stronger portfolio signal than a conventional CRUD backend would. + +**Revisit if**: in-browser PCA/UMAP/clustering performance can't hit V4's "fluid on a standard laptop" target, or a genuinely compelling shared/multi-user feature emerges that justifies the added complexity. diff --git a/docs/adr/0002-vertical-spike-before-full-buildout.md b/docs/adr/0002-vertical-spike-before-full-buildout.md new file mode 100644 index 0000000..b5adce7 --- /dev/null +++ b/docs/adr/0002-vertical-spike-before-full-buildout.md @@ -0,0 +1,5 @@ +# Vertical spike before full V1-V4 buildout + +The V4 spec is written as a consumer contract against V3's `SpatialEmbedding`, but none of V1 through V4 is built yet. Rather than building each version fully in sequence (V1 → V2 → V3 → V4), we're building a thin end-to-end slice first: `Grid → FeatureExtractor → GeometryDescriptor → PCA → SpatialEmbedding-shaped coordinates → a bare three.js point cloud`, deferring clustering, the Ship/Autopilot/HUD navigation layer, and every other page (`/evaluation`, `/draws`, `/geometry`, `/laboratory`, `/discovery`) until the spike proves the contract out. + +A mismatch discovered in the `SpatialEmbedding` shape only once real 3D rendering is attempted against it would be far more expensive to fix after V1-V3 were already fully built in isolation. diff --git a/e2e/evaluation-to-geometry.spec.ts b/e2e/evaluation-to-geometry.spec.ts new file mode 100644 index 0000000..d428a2e --- /dev/null +++ b/e2e/evaluation-to-geometry.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from '@playwright/test'; + +test('saisie -> évaluation -> géométrie', async ({ page }) => { + await page.goto('/evaluation'); + + const numbers = [3, 7, 19, 31, 42]; + const stars = [2, 9]; + for (const [index, value] of numbers.entries()) { + await page.getByTestId(`number-input-${index}`).fill(String(value)); + } + for (const [index, value] of stars.entries()) { + await page.getByTestId(`star-input-${index}`).fill(String(value)); + } + await page.getByTestId('evaluate-button').click(); + + await expect(page.getByTestId('evaluation-results')).toBeVisible(); + await expect(page.getByTestId('score-card')).toHaveCount(4); + await expect(page.getByTestId('variations').locator('li')).toHaveCount(3); + await expect(page.getByTestId('non-predictive-disclaimer')).toBeVisible(); + + await page.getByRole('link', { name: 'Géométrie' }).click(); + + await expect(page).toHaveURL(/\/geometry$/); + await expect(page.getByTestId('gap-map')).toBeVisible(); + await expect(page.getByTestId('neighbor-row').first()).toBeVisible(); + await expect(page.getByTestId('non-predictive-disclaimer')).toBeVisible(); +}); diff --git a/e2e/laboratory-backtest.spec.ts b/e2e/laboratory-backtest.spec.ts new file mode 100644 index 0000000..e70d703 --- /dev/null +++ b/e2e/laboratory-backtest.spec.ts @@ -0,0 +1,17 @@ +import { expect, test } from '@playwright/test'; + +test('construire une stratégie -> lancer le backtest -> voir les résultats', async ({ page }) => { + await page.goto('/laboratory'); + + await page.getByTestId('rule-toggle-above-31').check(); + await page.getByTestId('window-toggle-1').check(); + + await page.getByTestId('run-experiment-button').click(); + + await expect(page.getByTestId('experiment-results')).toBeVisible({ timeout: 15000 }); + await expect(page.getByTestId('baseline-comparison').locator('tbody tr')).toHaveCount(5); + await expect(page.getByTestId('monte-carlo')).toBeVisible(); + await expect(page.getByTestId('generated-grid-row').first()).toBeVisible(); + await expect(page.getByTestId('experiment-row')).toHaveCount(1); + await expect(page.getByTestId('non-predictive-disclaimer')).toBeVisible(); +}); diff --git a/package.json b/package.json index 4a6b9c1..dfb8291 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,71 @@ { "name": "izeetok", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", + "packageManager": "pnpm@11.0.9", + "engines": { + "node": "24.x" + }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "oxlint", - "preview": "vite preview" + "preview": "vite preview", + "prepare": "husky", + "lint:fix": "oxlint --fix", + "check": "tsc -b", + "test": "vitest", + "test:run": "vitest run", + "test:e2e": "playwright test", + "release:patch": "dx-flow release patch", + "release:minor": "dx-flow release minor", + "release:major": "dx-flow release major", + "qa:new": "dx-flow qa:new", + "qa:validate": "dx-flow qa:validate", + "qa:reset": "dx-flow qa:reset" }, "dependencies": { + "@react-three/drei": "^10.7.8", + "@react-three/fiber": "^9.7.0", + "@tanstack/react-query": "^5.101.4", + "d3": "^7.9.0", + "ml-kmeans": "^7.0.1", + "ml-pca": "^4.1.1", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "react-router": "^8.3.0", + "recharts": "^3.10.1", + "three": "^0.185.1", + "zod": "^4.4.3" }, "devDependencies": { + "@commitlint/cli": "^21.2.1", + "@commitlint/config-conventional": "^21.2.0", + "@keyobs/dx-flow": "3.1.3", + "@playwright/test": "^1.62.1", + "@testing-library/jest-dom": "^7.0.0", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.3", + "@types/d3": "^7.4.3", "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", + "@types/three": "^0.185.4", "@vitejs/plugin-react": "^6.0.4", + "husky": "^9.1.7", + "jsdom": "^30.0.1", + "lint-staged": "^17.3.0", "oxlint": "^1.75.0", + "playwright": "^1.62.1", + "sass": "^1.102.0", "typescript": "~6.0.2", - "vite": "^8.2.0" + "vite": "^8.2.0", + "vitest": "^4.1.10" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "oxlint --fix" + ] } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..73f4574 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from '@playwright/test'; + +const PORT = 5183; + +export default defineConfig({ + testDir: './e2e', + use: { + baseURL: `http://localhost:${PORT}`, + channel: 'chrome', + }, + webServer: { + command: `pnpm exec vite --port ${PORT}`, + url: `http://localhost:${PORT}`, + reuseExistingServer: true, + timeout: 30_000, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a76ffd6..8d36765 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,13 +8,67 @@ importers: .: dependencies: + '@react-three/drei': + specifier: ^10.7.8 + version: 10.7.8(@react-three/fiber@9.7.0(@types/react@19.2.18)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1))(@types/react@19.2.18)(@types/three@0.185.4)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1) + '@react-three/fiber': + specifier: ^9.7.0 + version: 9.7.0(@types/react@19.2.18)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1) + '@tanstack/react-query': + specifier: ^5.101.4 + version: 5.101.4(react@19.2.8) + d3: + specifier: ^7.9.0 + version: 7.9.0 + ml-kmeans: + specifier: ^7.0.1 + version: 7.0.1 + ml-pca: + specifier: ^4.1.1 + version: 4.1.1 react: specifier: ^19.2.8 version: 19.2.8 react-dom: specifier: ^19.2.8 version: 19.2.8(react@19.2.8) + react-router: + specifier: ^8.3.0 + version: 8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + recharts: + specifier: ^3.10.1 + version: 3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1) + three: + specifier: ^0.185.1 + version: 0.185.1 + zod: + specifier: ^4.4.3 + version: 4.4.3 devDependencies: + '@commitlint/cli': + specifier: ^21.2.1 + version: 21.2.1(@types/node@24.13.3)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + '@commitlint/config-conventional': + specifier: ^21.2.0 + version: 21.2.0 + '@keyobs/dx-flow': + specifier: 3.1.3 + version: 3.1.3 + '@playwright/test': + specifier: ^1.62.1 + version: 1.62.1 + '@testing-library/jest-dom': + specifier: ^7.0.0 + version: 7.0.0(@testing-library/dom@10.4.1) + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@testing-library/user-event': + specifier: ^14.6.3 + version: 14.6.3(@testing-library/dom@10.4.1) + '@types/d3': + specifier: ^7.4.3 + version: 7.4.3 '@types/node': specifier: ^24.13.3 version: 24.13.3 @@ -24,21 +78,218 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.4(@types/react@19.2.18) + '@types/three': + specifier: ^0.185.4 + version: 0.185.4 '@vitejs/plugin-react': specifier: ^6.0.4 - version: 6.0.5(vite@8.2.0(@types/node@24.13.3)) + version: 6.0.5(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0)) + husky: + specifier: ^9.1.7 + version: 9.1.7 + jsdom: + specifier: ^30.0.1 + version: 30.0.1 + lint-staged: + specifier: ^17.3.0 + version: 17.3.0 oxlint: specifier: ^1.75.0 version: 1.77.0 + playwright: + specifier: ^1.62.1 + version: 1.62.1 + sass: + specifier: ^1.102.0 + version: 1.102.0 typescript: specifier: ~6.0.2 version: 6.0.3 vite: specifier: ^8.2.0 - version: 8.2.0(@types/node@24.13.3) + version: 8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0) + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@24.13.3)(jsdom@30.0.1)(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0)) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@commitlint/cli@21.2.1': + resolution: {integrity: sha512-blsZGe29hJ72VGEFVl72IVYX+1vsfINpjA9yWQA6i7OKD/McGEOXg08sKIRKjFk4JvzhV/9n0l3i6NooPLTNfg==} + engines: {node: '>=22.12.0'} + hasBin: true + + '@commitlint/config-conventional@21.2.0': + resolution: {integrity: sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/config-validator@21.2.0': + resolution: {integrity: sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==} + engines: {node: '>=22.12.0'} + + '@commitlint/ensure@21.2.0': + resolution: {integrity: sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==} + engines: {node: '>=22.12.0'} + + '@commitlint/execute-rule@21.0.1': + resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} + engines: {node: '>=22.12.0'} + + '@commitlint/format@21.2.0': + resolution: {integrity: sha512-c4q64xaav2U83t7k7RyzJerBZurPer7FxUOY0RL5L/6CZijZ7K+s6HIBGIghj0ey1P2+seRX0J9XQYtDued6tg==} + engines: {node: '>=22.12.0'} + + '@commitlint/is-ignored@21.2.0': + resolution: {integrity: sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==} + engines: {node: '>=22.12.0'} + + '@commitlint/lint@21.2.0': + resolution: {integrity: sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==} + engines: {node: '>=22.12.0'} + + '@commitlint/load@21.2.0': + resolution: {integrity: sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==} + engines: {node: '>=22.12.0'} + + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} + engines: {node: '>=22.12.0'} + + '@commitlint/parse@21.2.0': + resolution: {integrity: sha512-QHWxG4d0PLTF634/AdyZ0MQS+CLn5YOuJlCFhMMlSGKFxzYGUetkHBj18xgBD+6fVzUrA2lrCdi/vlS2f/oYXg==} + engines: {node: '>=22.12.0'} + + '@commitlint/read@21.2.1': + resolution: {integrity: sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==} + engines: {node: '>=22.12.0'} + + '@commitlint/resolve-extends@21.2.0': + resolution: {integrity: sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==} + engines: {node: '>=22.12.0'} + + '@commitlint/rules@21.2.0': + resolution: {integrity: sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==} + engines: {node: '>=22.12.0'} + + '@commitlint/to-lines@21.0.1': + resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} + engines: {node: '>=22.12.0'} + + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} + engines: {node: '>=22.12.0'} + + '@commitlint/types@21.2.0': + resolution: {integrity: sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==} + engines: {node: '>=22.12.0'} + + '@conventional-changelog/git-client@3.1.1': + resolution: {integrity: sha512-w/q+UIVdWQMgXlziPIYfPlyDud+H8kcvSCzDQQoc/gid7yZzb6eNfAkNN4UlEcS2cVVh924jevsOIb36d0In2g==} + engines: {node: '>=22'} + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.1.2 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@conventional-changelog/template@1.2.1': + resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==} + engines: {node: '>=22'} + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.10': + resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7': + resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@dimforge/rapier3d-compat@0.12.0': + resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} + + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@keyobs/dx-flow@3.1.3': + resolution: {integrity: sha512-e0wMZ3cv3dpHHvq2UAfDDxQezV7zdV2aFBSM/iG/i73q7EMdAJkxjPP/scrIhla7bSKc3qVFOVTMhvKB3vyRyw==, tarball: https://npm.pkg.github.com/download/@keyobs/dx-flow/3.1.3/a9d82ad83e1ceea8319c51e7f6e618929f3a1698} + engines: {node: '>=22.21.1'} + hasBin: true + + '@mediapipe/tasks-vision@0.10.17': + resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} + + '@monogrid/gainmap-js@3.4.0': + resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==} + peerDependencies: + three: '>= 0.159.0' + '@oxc-project/types@0.143.0': resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} @@ -164,6 +415,140 @@ packages: cpu: [x64] os: [win32] + '@parcel/watcher-android-arm64@2.6.0': + resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.6.0': + resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.6.0': + resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.6.0': + resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.6.0': + resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.6.0': + resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.6.0': + resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.6.0': + resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.6.0': + resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.6.0': + resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-x64@2.6.0': + resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.6.0': + resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + engines: {node: '>= 10.0.0'} + + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} + hasBin: true + + '@react-three/drei@10.7.8': + resolution: {integrity: sha512-rJXyuzLm2Xq0kafHuR47ajDGbOe/pEhzIr4m8E8zwzQs0iNjloFDqBwRhrXmP/w+onLeYyN3EYPFW/cwWK/4yA==} + peerDependencies: + '@react-three/fiber': ^9.0.0 + react: ^19 + react-dom: ^19 + three: '>=0.159' + peerDependenciesMeta: + react-dom: + optional: true + + '@react-three/fiber@9.7.0': + resolution: {integrity: sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==} + peerDependencies: + expo: '>=43.0' + expo-asset: '>=8.4' + expo-file-system: '>=11.0' + expo-gl: '>=11.0' + react: '>=19 <19.3' + react-dom: '>=19 <19.3' + react-native: '>=0.78' + three: '>=0.156' + peerDependenciesMeta: + expo: + optional: true + expo-asset: + optional: true + expo-file-system: + optional: true + expo-gl: + optional: true + react-dom: + optional: true + react-native: + optional: true + + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/binding-android-arm64@1.2.3': resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -257,17 +642,212 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} + + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@tanstack/query-core@5.101.4': + resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} + + '@tanstack/react-query@5.101.4': + resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} + peerDependencies: + react: ^18 || ^19 + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@7.0.0': + resolution: {integrity: sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==} + engines: {node: '>=22', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@testing-library/dom': '>=10 <11' + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.3': + resolution: {integrity: sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tweenjs/tween.js@23.1.3': + resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/draco3d@1.4.10': + resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} + '@types/react-dom@19.2.4': resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-reconciler@0.28.9': + resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} + peerDependencies: + '@types/react': '*' + '@types/react@19.2.18': resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + '@types/stats.js@0.17.4': + resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} + + '@types/three@0.185.4': + resolution: {integrity: sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/webxr@0.5.24': + resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + + '@use-gesture/core@10.3.1': + resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} + + '@use-gesture/react@10.3.1': + resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} + peerDependencies: + react: '>= 16.8.0' + '@vitejs/plugin-react@6.0.5': resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -281,338 +861,2333 @@ packages: babel-plugin-react-compiler: optional: true - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: - picomatch: ^3 || ^4 + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: - picomatch: + msw: + optional: true + vite: optional: true - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] + argue-cli@3.1.0: + resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + engines: {node: '>=22'} - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - nanoid@3.3.17: - resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} - oxlint@1.77.0: - resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} - engines: {node: ^20.19.0 || >=22.12.0} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + binary-search@1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camera-controls@3.1.2: + resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} + engines: {node: '>=22.0.0', npm: '>=10.5.1'} + peerDependencies: + three: '>=0.126.1' + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + cheminfo-types@1.15.0: + resolution: {integrity: sha512-shv45WN2u0yN9EHH1bisNrv+fy4Cw+eLM5lOoriP67mePrwbHZ1kJqg90C8GEU7K1A8gJsicEoVZHcuBbuul/w==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + conventional-changelog-angular@9.2.1: + resolution: {integrity: sha512-oWSL6ZhnXbYraOFTK3PgRAQJ8fADDAEv5K6AdeyQPLvjFmhG8+ejL0jZZp/R7vTmGJaBvZEE+sE7dB4bCv7sAw==} + engines: {node: '>=22'} + + conventional-changelog-conventionalcommits@10.2.1: + resolution: {integrity: sha512-n4Kr1HFMTf3iMbES0TMxKIcYtUUv4rKqyQQp2JwfOEfFCOfGT3Tq4mCyJ8S9/YPyWhydjfKrrvnyl+gCjA+mJQ==} + engines: {node: '>=22'} + + conventional-commits-parser@7.1.2: + resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} + engines: {node: '>=22'} hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} + engines: {node: '>=v18'} peerDependencies: - oxlint-tsgolint: '>=7.0.2001' - vite-plus: '*' + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' peerDependenciesMeta: - oxlint-tsgolint: - optional: true - vite-plus: + typescript: optional: true - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} - engines: {node: ^10 || ^12 || >=14} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} - react-dom@19.2.8: - resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} - peerDependencies: - react: ^19.2.8 + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} - react@19.2.8: - resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} - engines: {node: '>=0.10.0'} + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} - rolldown@1.2.3: - resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} - engines: {node: ^20.19.0 || >=22.12.0} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} hasBin: true - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-gpu@5.0.70: + resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + + es-toolkit@1.50.0: + resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 + picomatch: ^3 || ^4 peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: + picomatch: optional: true -snapshots: + fflate@0.6.11: + resolution: {integrity: sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==} - '@oxc-project/types@0.143.0': {} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - '@oxlint/binding-android-arm-eabi@1.77.0': - optional: true + fft.js@4.0.4: + resolution: {integrity: sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==} - '@oxlint/binding-android-arm64@1.77.0': - optional: true + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - '@oxlint/binding-darwin-arm64@1.77.0': - optional: true + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - '@oxlint/binding-darwin-x64@1.77.0': - optional: true + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - '@oxlint/binding-freebsd-x64@1.77.0': - optional: true + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} - '@oxlint/binding-linux-arm-gnueabihf@1.77.0': - optional: true + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} - '@oxlint/binding-linux-arm-musleabihf@1.77.0': - optional: true + glsl-noise@0.0.0: + resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} - '@oxlint/binding-linux-arm64-gnu@1.77.0': - optional: true + hls.js@1.6.17: + resolution: {integrity: sha512-NUplVGVuc1hSPwdB/9/cbRkUmLrYi75/hqiXKdA+l300pJNxDu96R7jRb2imDzWJqIUF4I5ThmAdp9GvOCXsuQ==} - '@oxlint/binding-linux-arm64-musl@1.77.0': - optional: true + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@oxlint/binding-linux-ppc64-gnu@1.77.0': - optional: true + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + immer@11.1.16: + resolution: {integrity: sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==} + + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-any-array@3.0.0: + resolution: {integrity: sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + its-fine@2.0.0: + resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} + peerDependencies: + react: ^19.0.0 + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@17.3.0: + resolution: {integrity: sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==} + engines: {node: '>=22.22.1'} + hasBin: true + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + maath@0.10.8: + resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} + peerDependencies: + '@types/three': '>=0.134.0' + three: '>=0.134.0' + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + meshline@3.3.1: + resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} + peerDependencies: + three: '>=0.137' + + meshoptimizer@1.1.1: + resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + ml-array-max@2.0.0: + resolution: {integrity: sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==} + + ml-array-min@2.0.0: + resolution: {integrity: sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==} + + ml-array-rescale@2.0.0: + resolution: {integrity: sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==} + + ml-distance-euclidean@3.0.1: + resolution: {integrity: sha512-jEEu/1a73ArPmIiwOzrcah6TfhtV19dCKnnM7JvdR2xTzyVJFGgIIR78Vg8Pl9z2NVeSRoFOpzc0910sPMINsA==} + + ml-kmeans@7.0.1: + resolution: {integrity: sha512-yrBTntVdMf9gdMQnHBY1HQ5IhFdTAW8hcUhagiktqmx3lwR8VC2ONyP9oJfaVzMkummOd/XKgT64E2DgJlmfrQ==} + + ml-matrix@6.15.0: + resolution: {integrity: sha512-wFa1v6KP8bKp+fj0nYmRs1Pb5K4zRkXGKsOvLinvILENFIADncm4XlOI+S1M7yuACMGfI6cfk0IifDgd4j5xmw==} + + ml-nearest-vector@3.0.1: + resolution: {integrity: sha512-7UQ60sPS1Dpe6N6zuEsaL3nrUGHkaiSmjWcQHfMYFfV1dgp6TiPtv3ZtdLHwpf1jS0KQDSkw9pycJO1gYBiEoA==} + + ml-pca@4.1.1: + resolution: {integrity: sha512-HQwswMK1dObj+ppk3EPcQMR2djWK0Cri8mAFd2nITtXHkLfO4DBBsEtiCT5KiR+2e3hQjp+lI0UyqZpdf04AlQ==} + + ml-random@2.0.0: + resolution: {integrity: sha512-/ziHm0qyvsvwRy0y+EhmhTKbvq+9ghqf9UYSexKeyx8pp+9avDBLctYaopdTaJ1HU4qnJPEy7XH0CVeh9tHIgQ==} + + ml-spectra-processing@14.33.0: + resolution: {integrity: sha512-zg71v6exRvPpzDh9SiN5iNAIG8yTFpzfJrRCRSb/hWf5gMsik7q60DkO5ew4knR7xF4DbjYmNWeqLq8gWcQdEQ==} + + ml-xsadd@3.0.1: + resolution: {integrity: sha512-Fz2q6dwgzGM8wYKGArTUTZDGa4lQFA2Vi6orjGeTVRy22ZnQFKlJuwS9n8NRviqz1KHAHAzdKJwbnYhdo38uYg==} + + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} + hasBin: true + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + promise-worker-transferable@1.0.4: + resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + peerDependencies: + react: ^19.2.8 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-router@8.3.0: + resolution: {integrity: sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==} + engines: {node: '>=22.22.0'} + peerDependencies: + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true + + react-use-measure@2.1.7: + resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} + peerDependencies: + react: '>=16.13' + react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + engines: {node: '>=0.10.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + recharts@3.10.1: + resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + rolldown@1.2.3: + resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.102.0: + resolution: {integrity: sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==} + engines: {node: '>=20.19.0'} + hasBin: true + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stats-gl@2.4.2: + resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} + peerDependencies: + '@types/three': '*' + three: '*' + + stats.js@0.17.0: + resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + engines: {node: '>=20'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + suspend-react@0.1.3: + resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} + peerDependencies: + react: '>=17.0' + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + three-mesh-bvh@0.8.3: + resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} + peerDependencies: + three: '>= 0.159.0' + + three-stdlib@2.36.1: + resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} + peerDependencies: + three: '>=0.128.0' + + three@0.185.1: + resolution: {integrity: sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} + + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} + hasBin: true + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + troika-three-text@0.52.5: + resolution: {integrity: sha512-Ry3jRhic9pzcY4JduSvRRyDmVOSqEW19gT4vtK+aCiPNVcDlmkxvGG0YbFd36RTDq1wExOupXnvNF/j1oiHHDA==} + peerDependencies: + three: '>=0.125.0' + + troika-three-utils@0.52.5: + resolution: {integrity: sha512-WsePbcX8RtfidRfsxK1eCZCjF81ZDzAKHH/evLs0hdV2wpoCb0vArGZHdzdOJrSS3k4zfdtbKDaBh8+phkrYnw==} + peerDependencies: + three: '>=0.125.0' + + troika-worker-utils@0.52.0: + resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} + + tunnel-rat@0.1.2: + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + + vite@8.2.0: + resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webgl-constants@1.1.1: + resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} + + webgl-sdf-generator@1.1.1: + resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zustand@5.0.14: + resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@adobe/css-tools@4.5.0': {} + + '@asamuzakjp/css-color@6.0.7': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/runtime@7.29.7': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@commitlint/cli@21.2.1(@types/node@24.13.3)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + dependencies: + '@commitlint/config-conventional': 21.2.0 + '@commitlint/format': 21.2.0 + '@commitlint/lint': 21.2.0 + '@commitlint/load': 21.2.0(@types/node@24.13.3)(typescript@6.0.3) + '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.2) + '@commitlint/types': 21.2.0 + tinyexec: 1.3.0 + yargs: 18.1.0 + transitivePeerDependencies: + - '@types/node' + - conventional-commits-filter + - conventional-commits-parser + - typescript + + '@commitlint/config-conventional@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + conventional-changelog-conventionalcommits: 10.2.1 + + '@commitlint/config-validator@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + ajv: 8.20.0 + + '@commitlint/ensure@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + es-toolkit: 1.50.0 + + '@commitlint/execute-rule@21.0.1': {} + + '@commitlint/format@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + picocolors: 1.1.1 + + '@commitlint/is-ignored@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + semver: 7.8.5 + + '@commitlint/lint@21.2.0': + dependencies: + '@commitlint/is-ignored': 21.2.0 + '@commitlint/parse': 21.2.0 + '@commitlint/rules': 21.2.0 + '@commitlint/types': 21.2.0 + + '@commitlint/load@21.2.0(@types/node@24.13.3)(typescript@6.0.3)': + dependencies: + '@commitlint/config-validator': 21.2.0 + '@commitlint/execute-rule': 21.0.1 + '@commitlint/resolve-extends': 21.2.0 + '@commitlint/types': 21.2.0 + cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@24.13.3)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + es-toolkit: 1.50.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@21.2.0': {} + + '@commitlint/parse@21.2.0': + dependencies: + '@commitlint/types': 21.2.0 + conventional-changelog-angular: 9.2.1 + conventional-commits-parser: 7.1.2 + + '@commitlint/read@21.2.1(conventional-commits-parser@7.1.2)': + dependencies: + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.0 + '@conventional-changelog/git-client': 3.1.1(conventional-commits-parser@7.1.2) + tinyexec: 1.3.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + + '@commitlint/resolve-extends@21.2.0': + dependencies: + '@commitlint/config-validator': 21.2.0 + '@commitlint/types': 21.2.0 + es-toolkit: 1.50.0 + global-directory: 5.0.0 + resolve-from: 5.0.0 + + '@commitlint/rules@21.2.0': + dependencies: + '@commitlint/ensure': 21.2.0 + '@commitlint/message': 21.2.0 + '@commitlint/to-lines': 21.0.1 + '@commitlint/types': 21.2.0 + + '@commitlint/to-lines@21.0.1': {} + + '@commitlint/top-level@21.2.0': + dependencies: + escalade: 3.2.0 + + '@commitlint/types@21.2.0': + dependencies: + conventional-commits-parser: 7.1.2 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@3.1.1(conventional-commits-parser@7.1.2)': + dependencies: + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 7.1.2 + + '@conventional-changelog/template@1.2.1': {} + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@dimforge/rapier3d-compat@0.12.0': {} + + '@exodus/bytes@1.15.1': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@keyobs/dx-flow@3.1.3': {} + + '@mediapipe/tasks-vision@0.10.17': {} + + '@monogrid/gainmap-js@3.4.0(three@0.185.1)': + dependencies: + promise-worker-transferable: 1.0.4 + three: 0.185.1 + + '@oxc-project/types@0.143.0': {} + + '@oxlint/binding-android-arm-eabi@1.77.0': + optional: true + + '@oxlint/binding-android-arm64@1.77.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.77.0': + optional: true + + '@oxlint/binding-darwin-x64@1.77.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.77.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.77.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.77.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.77.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.77.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.77.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.77.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.77.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.77.0': + optional: true + + '@parcel/watcher-android-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-x64@2.6.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.6.0': + optional: true + + '@parcel/watcher-win32-arm64@2.6.0': + optional: true + + '@parcel/watcher-win32-x64@2.6.0': + optional: true + + '@parcel/watcher@2.6.0': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.5 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.6.0 + '@parcel/watcher-darwin-arm64': 2.6.0 + '@parcel/watcher-darwin-x64': 2.6.0 + '@parcel/watcher-freebsd-x64': 2.6.0 + '@parcel/watcher-linux-arm-glibc': 2.6.0 + '@parcel/watcher-linux-arm-musl': 2.6.0 + '@parcel/watcher-linux-arm64-glibc': 2.6.0 + '@parcel/watcher-linux-arm64-musl': 2.6.0 + '@parcel/watcher-linux-x64-glibc': 2.6.0 + '@parcel/watcher-linux-x64-musl': 2.6.0 + '@parcel/watcher-win32-arm64': 2.6.0 + '@parcel/watcher-win32-x64': 2.6.0 + optional: true + + '@playwright/test@1.62.1': + dependencies: + playwright: 1.62.1 + + '@react-three/drei@10.7.8(@react-three/fiber@9.7.0(@types/react@19.2.18)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1))(@types/react@19.2.18)(@types/three@0.185.4)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@mediapipe/tasks-vision': 0.10.17 + '@monogrid/gainmap-js': 3.4.0(three@0.185.1) + '@react-three/fiber': 9.7.0(@types/react@19.2.18)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1) + '@use-gesture/react': 10.3.1(react@19.2.8) + camera-controls: 3.1.2(three@0.185.1) + cross-env: 7.0.3 + detect-gpu: 5.0.70 + glsl-noise: 0.0.0 + hls.js: 1.6.17 + maath: 0.10.8(@types/three@0.185.4)(three@0.185.1) + meshline: 3.3.1(three@0.185.1) + react: 19.2.8 + stats-gl: 2.4.2(@types/three@0.185.4)(three@0.185.1) + stats.js: 0.17.0 + suspend-react: 0.1.3(react@19.2.8) + three: 0.185.1 + three-mesh-bvh: 0.8.3(three@0.185.1) + three-stdlib: 2.36.1(three@0.185.1) + troika-three-text: 0.52.5(three@0.185.1) + tunnel-rat: 0.1.2(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8) + use-sync-external-store: 1.6.0(react@19.2.8) + utility-types: 3.11.0 + zustand: 5.0.14(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - '@types/three' + - immer + + '@react-three/fiber@9.7.0(@types/react@19.2.18)(immer@11.1.16)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(three@0.185.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@types/webxr': 0.5.24 + base64-js: 1.5.1 + buffer: 6.0.3 + its-fine: 2.0.0(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-use-measure: 2.1.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + scheduler: 0.27.0 + suspend-react: 0.1.3(react@19.2.8) + three: 0.185.1 + use-sync-external-store: 1.6.0(react@19.2.8) + zustand: 5.0.14(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1))(react@19.2.8)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.16 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.2.0 + optionalDependencies: + react: 19.2.8 + react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1) + + '@rolldown/binding-android-arm64@1.2.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.3': + optional: true + + '@rolldown/binding-darwin-x64@1.2.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.3': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@simple-libs/child-process-utils@2.0.0': + dependencies: + '@simple-libs/stream-utils': 2.0.0 + + '@simple-libs/stream-utils@2.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@tanstack/query-core@5.101.4': {} + + '@tanstack/react-query@5.101.4(react@19.2.8)': + dependencies: + '@tanstack/query-core': 5.101.4 + react: 19.2.8 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@7.0.0(@testing-library/dom@10.4.1)': + dependencies: + '@adobe/css-tools': 4.5.0 + '@testing-library/dom': 10.4.1 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) + + '@testing-library/user-event@14.6.3(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tweenjs/tween.js@23.1.3': {} + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.1': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.1 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/deep-eql@4.0.2': {} + + '@types/draco3d@1.4.10': {} + + '@types/estree@1.0.9': {} + + '@types/geojson@7946.0.16': {} + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/offscreencanvas@2019.7.3': {} + + '@types/react-dom@19.2.4(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react-reconciler@0.28.9(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/stats.js@0.17.4': {} + + '@types/three@0.185.4': + dependencies: + '@dimforge/rapier3d-compat': 0.12.0 + '@tweenjs/tween.js': 23.1.3 + '@types/stats.js': 0.17.4 + '@types/webxr': 0.5.24 + fflate: 0.8.3 + meshoptimizer: 1.1.1 + + '@types/use-sync-external-store@0.0.6': {} + + '@types/webxr@0.5.24': {} + + '@use-gesture/core@10.3.1': {} + + '@use-gesture/react@10.3.1(react@19.2.8)': + dependencies: + '@use-gesture/core': 10.3.1 + react: 19.2.8 + + '@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0) + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.5 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + argue-cli@3.1.0: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + base64-js@1.5.1: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + binary-search@1.3.6: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + callsites@3.1.0: {} + + camera-controls@3.1.2(three@0.185.1): + dependencies: + three: 0.185.1 + + chai@6.2.2: {} + + cheminfo-types@1.15.0: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + clsx@2.1.1: {} + + commander@7.2.0: {} + + conventional-changelog-angular@9.2.1: + dependencies: + '@conventional-changelog/template': 1.2.1 + + conventional-changelog-conventionalcommits@10.2.1: + dependencies: + '@conventional-changelog/template': 1.2.1 + + conventional-commits-parser@7.1.2: + dependencies: + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.1.0 + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + cosmiconfig-typescript-loader@6.3.0(@types/node@24.13.3)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + dependencies: + '@types/node': 24.13.3 + cosmiconfig: 9.0.2(typescript@6.0.3) + jiti: 2.6.1 + typescript: 6.0.3 + + cosmiconfig@9.0.2(typescript@6.0.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 6.0.3 + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + dequal@2.0.3: {} + + detect-gpu@5.0.70: + dependencies: + webgl-constants: 1.1.1 + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + draco3d@1.5.7: {} + + emoji-regex@10.6.0: {} + + entities@8.0.0: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@2.3.1: {} + + es-toolkit@1.50.0: {} + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eventemitter3@5.0.4: {} + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.5: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fflate@0.6.11: {} - '@oxlint/binding-linux-riscv64-gnu@1.77.0': - optional: true + fflate@0.8.3: {} - '@oxlint/binding-linux-riscv64-musl@1.77.0': - optional: true + fft.js@4.0.4: {} - '@oxlint/binding-linux-s390x-gnu@1.77.0': + fsevents@2.3.2: optional: true - '@oxlint/binding-linux-x64-gnu@1.77.0': + fsevents@2.3.3: optional: true - '@oxlint/binding-linux-x64-musl@1.77.0': - optional: true + get-caller-file@2.0.5: {} - '@oxlint/binding-openharmony-arm64@1.77.0': - optional: true + get-east-asian-width@1.6.0: {} - '@oxlint/binding-win32-arm64-msvc@1.77.0': - optional: true + global-directory@5.0.0: + dependencies: + ini: 6.0.0 - '@oxlint/binding-win32-ia32-msvc@1.77.0': - optional: true + glsl-noise@0.0.0: {} - '@oxlint/binding-win32-x64-msvc@1.77.0': - optional: true + hls.js@1.6.17: {} - '@rolldown/binding-android-arm64@1.2.3': - optional: true + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' - '@rolldown/binding-darwin-arm64@1.2.3': - optional: true + husky@9.1.7: {} - '@rolldown/binding-darwin-x64@1.2.3': - optional: true + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 - '@rolldown/binding-freebsd-x64@1.2.3': - optional: true + ieee754@1.2.1: {} - '@rolldown/binding-linux-arm-gnueabihf@1.2.3': - optional: true + immediate@3.0.6: {} - '@rolldown/binding-linux-arm64-gnu@1.2.3': - optional: true + immer@11.1.16: {} - '@rolldown/binding-linux-arm64-musl@1.2.3': - optional: true + immutable@5.1.9: {} - '@rolldown/binding-linux-ppc64-gnu@1.2.3': - optional: true + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 - '@rolldown/binding-linux-s390x-gnu@1.2.3': - optional: true + indent-string@4.0.0: {} - '@rolldown/binding-linux-x64-gnu@1.2.3': - optional: true + ini@6.0.0: {} - '@rolldown/binding-linux-x64-musl@1.2.3': - optional: true + internmap@2.0.3: {} - '@rolldown/binding-openharmony-arm64@1.2.3': - optional: true + is-any-array@3.0.0: {} - '@rolldown/binding-win32-arm64-msvc@1.2.3': + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: optional: true - '@rolldown/binding-win32-x64-msvc@1.2.3': + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 optional: true - '@rolldown/pluginutils@1.0.1': {} + is-plain-obj@4.1.0: {} - '@types/node@24.13.3': - dependencies: - undici-types: 7.18.2 + is-potential-custom-element-name@1.0.1: {} - '@types/react-dom@19.2.4(@types/react@19.2.18)': - dependencies: - '@types/react': 19.2.18 + is-promise@2.2.2: {} - '@types/react@19.2.18': - dependencies: - csstype: 3.2.3 + isexe@2.0.0: {} - '@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@24.13.3))': + its-fine@2.0.0(@types/react@19.2.18)(react@19.2.8): dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.0(@types/node@24.13.3) + '@types/react-reconciler': 0.28.9(@types/react@19.2.18) + react: 19.2.8 + transitivePeerDependencies: + - '@types/react' - csstype@3.2.3: {} + jiti@2.6.1: {} - detect-libc@2.1.2: {} + js-tokens@4.0.0: {} - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 - fsevents@2.3.3: - optional: true + jsdom@30.0.1: + dependencies: + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.10.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + lie@3.3.0: + dependencies: + immediate: 3.0.6 lightningcss-android-arm64@1.33.0: optional: true @@ -663,8 +3238,98 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 + lines-and-columns@1.2.4: {} + + lint-staged@17.3.0: + dependencies: + picomatch: 4.0.5 + string-argv: 0.3.2 + tinyexec: 1.3.0 + optionalDependencies: + yaml: 2.9.0 + + lru-cache@11.5.2: {} + + lz-string@1.5.0: {} + + maath@0.10.8(@types/three@0.185.4)(three@0.185.1): + dependencies: + '@types/three': 0.185.4 + three: 0.185.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mdn-data@2.27.1: {} + + meshline@3.3.1(three@0.185.1): + dependencies: + three: 0.185.1 + + meshoptimizer@1.1.1: {} + + min-indent@1.0.1: {} + + ml-array-max@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-min@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-rescale@2.0.0: + dependencies: + is-any-array: 3.0.0 + ml-array-max: 2.0.0 + ml-array-min: 2.0.0 + + ml-distance-euclidean@3.0.1: {} + + ml-kmeans@7.0.1: + dependencies: + ml-distance-euclidean: 3.0.1 + ml-matrix: 6.15.0 + ml-nearest-vector: 3.0.1 + ml-random: 2.0.0 + ml-spectra-processing: 14.33.0 + + ml-matrix@6.15.0: + dependencies: + is-any-array: 3.0.0 + ml-array-rescale: 2.0.0 + + ml-nearest-vector@3.0.1: + dependencies: + ml-distance-euclidean: 3.0.1 + + ml-pca@4.1.1: + dependencies: + ml-matrix: 6.15.0 + + ml-random@2.0.0: + dependencies: + ml-xsadd: 3.0.1 + + ml-spectra-processing@14.33.0: + dependencies: + binary-search: 1.3.6 + cheminfo-types: 1.15.0 + fft.js: 4.0.4 + is-any-array: 3.0.0 + ml-matrix: 6.15.0 + ml-xsadd: 3.0.1 + + ml-xsadd@3.0.1: {} + nanoid@3.3.17: {} + node-addon-api@7.1.1: + optional: true + + obug@2.1.4: {} + oxlint@1.77.0: optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.77.0 @@ -687,23 +3352,134 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.77.0 '@oxlint/binding-win32-x64-msvc': 1.77.0 + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-key@3.1.1: {} + + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@4.0.5: {} + playwright-core@1.62.1: {} + + playwright@1.62.1: + dependencies: + playwright-core: 1.62.1 + optionalDependencies: + fsevents: 2.3.2 + postcss@8.5.26: dependencies: nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 + potpack@1.0.2: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + promise-worker-transferable@1.0.4: + dependencies: + is-promise: 2.2.2 + lie: 3.3.0 + + punycode@2.3.1: {} + react-dom@19.2.8(react@19.2.8): dependencies: react: 19.2.8 scheduler: 0.27.0 + react-is@17.0.2: {} + + react-is@19.2.8: {} + + react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.8 + use-sync-external-store: 1.6.0(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + redux: 5.0.1 + + react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + cookie-es: 3.1.1 + react: 19.2.8 + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + + react-use-measure@2.1.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + react: 19.2.8 + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + react@19.2.8: {} + readdirp@5.1.1: {} + + recharts@3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1))(react@19.2.8) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.50.0 + eventemitter3: 5.0.4 + immer: 11.1.16 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-is: 19.2.8 + react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1) + reselect: 5.2.0 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.8) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + require-from-string@2.0.2: {} + + reselect@5.2.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + robust-predicates@3.0.3: {} + rolldown@1.2.3: dependencies: '@oxc-project/types': 0.143.0 @@ -724,20 +3500,169 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.3 '@rolldown/binding-win32-x64-msvc': 1.2.3 + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + sass@1.102.0: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.9 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.6.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + source-map-js@1.2.1: {} + stackback@0.0.2: {} + + stats-gl@2.4.2(@types/three@0.185.4)(three@0.185.1): + dependencies: + '@types/three': 0.185.4 + three: 0.185.1 + + stats.js@0.17.0: {} + + std-env@4.2.0: {} + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + string-width@8.2.2: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + suspend-react@0.1.3(react@19.2.8): + dependencies: + react: 19.2.8 + + symbol-tree@3.2.4: {} + + three-mesh-bvh@0.8.3(three@0.185.1): + dependencies: + three: 0.185.1 + + three-stdlib@2.36.1(three@0.185.1): + dependencies: + '@types/draco3d': 1.4.10 + '@types/offscreencanvas': 2019.7.3 + '@types/webxr': 0.5.24 + draco3d: 1.5.7 + fflate: 0.6.11 + potpack: 1.0.2 + three: 0.185.1 + + three@0.185.1: {} + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.3.0: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinyrainbow@3.1.1: {} + + tldts-core@7.4.10: {} + + tldts@7.4.10: + dependencies: + tldts-core: 7.4.10 + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.10 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + troika-three-text@0.52.5(three@0.185.1): + dependencies: + bidi-js: 1.0.3 + three: 0.185.1 + troika-three-utils: 0.52.5(three@0.185.1) + troika-worker-utils: 0.52.0 + webgl-sdf-generator: 1.1.1 + + troika-three-utils@0.52.5(three@0.185.1): + dependencies: + three: 0.185.1 + + troika-worker-utils@0.52.0: {} + + tunnel-rat@0.1.2(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8): + dependencies: + zustand: 4.5.7(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8) + transitivePeerDependencies: + - '@types/react' + - immer + - react + typescript@6.0.3: {} undici-types@7.18.2: {} - vite@8.2.0(@types/node@24.13.3): + undici@8.10.0: {} + + use-sync-external-store@1.6.0(react@19.2.8): + dependencies: + react: 19.2.8 + + utility-types@3.11.0: {} + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -747,3 +3672,114 @@ snapshots: optionalDependencies: '@types/node': 24.13.3 fsevents: 2.3.3 + jiti: 2.6.1 + sass: 1.102.0 + yaml: 2.9.0 + + vitest@4.1.10(@types/node@24.13.3)(jsdom@30.0.1)(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.0(@types/node@24.13.3)(jiti@2.6.1)(sass@1.102.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.3 + jsdom: 30.0.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webgl-constants@1.1.1: {} + + webgl-sdf-generator@1.1.1: {} + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yaml@2.9.0: + optional: true + + yargs-parser@22.0.0: {} + + yargs@18.1.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.2.2 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + zod@4.4.3: {} + + zustand@4.5.7(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + immer: 11.1.16 + react: 19.2.8 + + zustand@5.0.14(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)): + optionalDependencies: + '@types/react': 19.2.18 + immer: 11.1.16 + react: 19.2.8 + use-sync-external-store: 1.6.0(react@19.2.8) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..620a7bb --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + '@parcel/watcher': true diff --git a/public/results/euromillions_202002.csv b/public/results/euromillions_202002.csv new file mode 100644 index 0000000..3ca63f5 --- /dev/null +++ b/public/results/euromillions_202002.csv @@ -0,0 +1,681 @@ +annee_numero_de_tirage;jour_de_tirage;date_de_tirage;numéro_de_tirage_dans_le_cycle;date_de_forclusion;boule_1;boule_2;boule_3;boule_4;boule_5;etoile_1;etoile_2;boules_gagnantes_en_ordre_croissant;etoiles_gagnantes_en_ordre_croissant;nombre_de_gagnant_au_rang1_Euro_Millions_en_france;nombre_de_gagnant_au_rang1_Euro_Millions_en_europe;rapport_du_rang1_Euro_Millions;nombre_de_gagnant_au_rang2_Euro_Millions_en_france;nombre_de_gagnant_au_rang2_Euro_Millions_en_europe;rapport_du_rang2_Euro_Millions;nombre_de_gagnant_au_rang3_Euro_Millions_en_france;nombre_de_gagnant_au_rang3_Euro_Millions_en_europe;rapport_du_rang3_Euro_Millions;nombre_de_gagnant_au_rang4_Euro_Millions_en_france;nombre_de_gagnant_au_rang4_Euro_Millions_en_europe;rapport_du_rang4_Euro_Millions;nombre_de_gagnant_au_rang5_Euro_Millions_en_france;nombre_de_gagnant_au_rang5_Euro_Millions_en_europe;rapport_du_rang5_Euro_Millions;nombre_de_gagnant_au_rang6_Euro_Millions_en_france;nombre_de_gagnant_au_rang6_Euro_Millions_en_europe;rapport_du_rang6_Euro_Millions;nombre_de_gagnant_au_rang7_Euro_Millions_en_france;nombre_de_gagnant_au_rang7_Euro_Millions_en_europe;rapport_du_rang7_Euro_Millions;nombre_de_gagnant_au_rang8_Euro_Millions_en_france;nombre_de_gagnant_au_rang8_Euro_Millions_en_europe;rapport_du_rang8_Euro_Millions;nombre_de_gagnant_au_rang9_Euro_Millions_en_france;nombre_de_gagnant_au_rang9_Euro_Millions_en_europe;rapport_du_rang9_Euro_Millions;nombre_de_gagnant_au_rang10_Euro_Millions_en_france;nombre_de_gagnant_au_rang10_Euro_Millions_en_europe;rapport_du_rang10_Euro_Millions;nombre_de_gagnant_au_rang11_Euro_Millions_en_france;nombre_de_gagnant_au_rang11_Euro_Millions_en_europe;rapport_du_rang11_Euro_Millions;nombre_de_gagnant_au_rang12_Euro_Millions_en_france;nombre_de_gagnant_au_rang12_Euro_Millions_en_europe;rapport_du_rang12_Euro_Millions;nombre_de_gagnant_au_rang13_Euro_Millions_en_france;nombre_de_gagnant_au_rang13_Euro_Millions_en_europe;rapport_du_rang13_Euro_Millions;nombre_de_gagnant_au_rang1_Etoile+;rapport_du_rang1_Etoile+;nombre_de_gagnant_au_rang2_Etoile+;rapport_du_rang2_Etoile+;nombre_de_gagnant_au_rang3_Etoile+;rapport_du_rang3_Etoile+;nombre_de_gagnant_au_rang4_Etoile+;rapport_du_rang4_Etoile+;nombre_de_gagnant_au_rang5_Etoile+;rapport_du_rang5_Etoile+;nombre_de_gagnant_au_rang6_Etoile+;rapport_du_rang6_Etoile+;nombre_de_gagnant_au_rang7_Etoile+;rapport_du_rang7_Etoile+;nombre_de_gagnant_au_rang8_Etoile+;rapport_du_rang8_Etoile+;nombre_de_gagnant_au_rang9_Etoile+;rapport_du_rang9_Etoile+;nombre_de_gagnant_au_rang10_Etoile+;rapport_du_rang10_Etoile+;numero_My_Million;numero_Tirage_Exceptionnel_Euro_Million; +26063;VENDREDI;07/08/2026;10;06/11/2026;35;26;29;47;38;1;2;-26-29-35-38-47-;-1-2-;0;1;111058174,00;1;7;112479,10;2;6;30669,60;2;22;2605,30;142;723;146,00;328;1420;78,60;425;1855;42,20;4431;20313;19,30;7437;33369;13,10;17614;80063;10,10;24716;114617;8,60;113528;490858;6,30;260637;1156077;4,30;1;1841,70;1;1473,30;56;32,80;155;11,80;2223;2,00;3569;3,40;12292;4,70;22541;11,90;54722;2,70;570379;2,30;RZ 296 9256;; +26062;MARDI;04/08/2026;9;03/11/2026;25;50;34;46;30;1;12;-25-30-34-46-50-;-1-12-;0;0;0;0;3;192409,40;2;6;22484,60;3;20;2101,00;100;474;163,30;238;1106;73,90;228;1122;51,20;3645;17126;16,70;4129;20848;15,30;10227;52122;11,40;18391;87386;8,20;63740;316485;7,10;167371;824628;4,40;0;0;2;1148,10;62;20,50;117;10,90;1809;1,70;2101;4,00;9414;4,20;16458;11,30;32224;3,20;338542;2,70;FR 875 0961;; +26061;VENDREDI;31/07/2026;8;30/10/2026;10;25;31;24;45;5;4;-10-24-25-31-45-;-4-5-;0;0;0;0;2;343065,50;1;8;20045,00;7;38;1314,40;178;792;116,10;406;1725;56,30;341;1644;41,50;5067;24176;14,10;8293;37743;10,00;16330;74771;9,40;25693;122160;7,00;115187;529751;5,10;231941;1062178;4,10;0;0;4;682,40;95;15,90;202;7,50;2434;1,50;3937;2,50;12328;3,80;20172;10,90;55001;2,20;453076;2,40;AW 461 1722;; +26060;MARDI;28/07/2026;7;27/10/2026;7;5;49;24;30;11;10;-5-7-24-30-49-;-10-11-;0;0;0;0;1;522575,90;0;2;61067,30;6;29;1311,70;127;596;117,50;190;1010;73,30;365;1506;34,50;2825;14328;18,10;5596;25052;11,50;16263;68880;7,80;13934;73599;8,80;76756;350687;5,80;218080;959974;3,40;0;0;2;1026,10;69;16,50;111;10,20;1442;1,90;2768;2,70;7304;4,90;11446;14,50;38259;2,40;284968;2,90;GA 472 5470;; +26059;VENDREDI;24/07/2026;6;23/10/2026;47;30;10;36;8;1;4;-8-10-30-36-47-;-1-4-;0;0;0;1;1;645724,00;1;5;30183,20;2;19;2474,00;105;539;160,60;245;1267;72,20;261;1331;48,30;4071;19442;16,50;5563;27343;13,10;12952;64240;10,30;22487;104580;7,70;91113;423659;6,00;208660;986232;4,10;1;1416,80;0;0;54;47,20;126;11,20;1958;1,80;2769;3,40;10694;4,10;17708;11,60;44521;2,60;401288;2,50;SD 826 3887;; +26058;MARDI;21/07/2026;5;20/10/2026;39;8;28;2;3;11;2;-2-3-8-28-39-;-2-11-;0;0;0;0;2;250104,30;0;2;58453,50;4;19;1916,50;118;606;110,60;274;1224;57,90;226;1150;43,30;3691;17520;14,20;5621;26369;10,50;12408;58007;8,90;18754;89922;6,90;77647;365439;5,40;181107;836260;3,80;0;0;3;651,00;58;18,70;135;8,00;1841;1,40;2761;2,60;9334;3,60;14710;10,70;38258;2,30;295061;2,60;HF 658 0122;; +26057;VENDREDI;17/07/2026;4;16/10/2026;34;21;40;12;23;10;9;-12-21-23-34-40-;-9-10-;0;0;0;0;0;0;3;7;111131,40;4;21;2185,80;135;726;116,40;236;1149;77,70;329;1679;37,40;3175;16279;19,20;6445;31505;11,10;16764;75769;8,60;16723;86415;9,10;92518;442664;5,60;230113;1065262;3,70;0;0;3;829,30;76;18,10;119;11,60;1627;2,10;3201;2,80;8910;4,80;14841;13,50;46074;2,40;391983;2,50;MA 638 4778;; +26056;MARDI;14/07/2026;3;13/10/2026;42;10;19;37;47;9;12;-10-19-37-42-47-;-9-12-;0;0;0;1;2;238156,40;1;5;22264,40;5;24;1444,70;95;467;136,70;157;866;77,90;266;1168;40,60;2454;12776;18,50;3870;20400;12,90;10182;50012;9,80;12919;66955;8,90;59763;301034;6,20;158494;752440;4,00;0;0;3;607,60;43;23,50;83;12,20;1313;1,90;1942;3,40;6845;4,60;11401;12,90;30116;2,70;280883;2,60;DR 329 0687;; +26055;VENDREDI;10/07/2026;2;09/10/2026;2;28;14;33;48;8;10;-2-14-28-33-48-;-8-10-;0;0;0;0;4;149698,40;1;7;19992,50;6;32;1362,10;120;659;121,80;242;1186;71,50;258;1351;44,10;3729;17535;17,00;5761;27622;12,00;13599;64779;9,50;20234;93242;8,00;85221;400598;5,80;201800;940529;4,00;0;0;3;815,60;61;22,20;142;9,50;1919;1,70;2908;3,10;10733;3,90;18087;10,90;43640;2,50;384121;2,50;UP 040 3568;; +26054;MARDI;07/07/2026;1;06/10/2026;45;5;33;29;47;8;5;-5-29-33-45-47-;-5-8-;0;0;0;0;3;150509,80;2;5;21105,90;5;27;1217,40;100;481;125,80;273;1398;45,70;199;965;46,60;4483;20917;10,70;4356;21114;11,80;9098;43565;10,70;24687;114244;4,90;66941;316806;5,60;138870;646656;4,40;0;0;3;624,50;49;21,20;139;7,40;2115;1,20;2187;3,10;11979;2,70;19339;7,80;33694;2,50;313614;2,40;RC 137 6956;; +26053;VENDREDI;03/07/2026;7;02/10/2026;12;2;25;39;17;2;1;-2-12-17-25-39-;-1-2-;0;1;80220498,00;1;3;233586,20;1;9;18197,60;5;50;1020,20;259;996;94,30;355;1600;62,00;509;2073;33,60;4947;22846;15,20;9783;40821;9,50;20191;88162;8,20;23906;113324;7,70;128003;543566;5,00;270477;1195498;3,70;1;1596,10;4;319,20;117;13,60;172;9,20;2429;1,60;4711;2,20;11714;4,20;17277;13,40;62041;2,10;450660;2,50;DB 464 8307;; +26052;MARDI;30/06/2026;6;29/09/2026;37;48;44;1;8;6;2;-1-8-37-44-48-;-2-6-;0;0;0;1;2;268526,70;0;8;15689,70;6;24;1628,90;86;429;167,80;260;1201;63,30;189;907;58,90;4137;18644;14,30;4656;21803;13,60;10073;48350;11,40;24311;108275;6,20;75337;348128;6,00;161651;768876;4,40;1;1177,30;2;470,90;50;23,50;127;9,20;2032;1,40;2374;3,30;11398;3,20;20610;8,30;37073;2,60;368815;2,30;UY 611 2890;; +26051;VENDREDI;26/06/2026;5;25/09/2026;6;35;16;26;34;11;12;-6-16-26-34-35-;-11-12-;0;0;0;0;3;216058,50;1;12;12624,10;0;24;1966,00;100;542;160,30;201;1067;86,10;329;1726;37,40;2912;15988;20,10;5045;26452;13,60;16304;76248;8,70;15791;85067;9,50;74321;375348;6,80;232527;1065466;3,80;0;0;0;0;56;70,90;106;13,30;1610;2,20;2652;3,50;8752;5,10;14592;14,20;38719;3,00;356323;2,80;TE 244 5417;; +26050;MARDI;23/06/2026;4;22/09/2026;45;36;33;3;46;5;6;-3-33-36-45-46-;-5-6-;0;0;0;1;3;166281,50;1;5;23317,60;5;22;1650,60;77;408;163,90;155;986;71,70;167;894;55,50;2671;14697;16,90;3930;20811;13,30;8528;44050;11,70;15760;82965;7,50;65148;325105;6,00;133285;672893;4,70;1;1103,80;3;294,30;48;22,90;76;14,50;1389;1,90;1959;3,70;7960;4,30;15785;10,20;32809;2,70;372035;2,10;LN 170 7243;; +26049;VENDREDI;19/06/2026;3;18/09/2026;39;41;42;8;34;2;7;-8-34-39-41-42-;-2-7-;0;0;0;0;0;0;1;7;110334,50;3;19;2398,50;103;453;185,30;411;1531;57,90;215;1110;56,10;6030;23965;13,00;5596;26024;13,30;11461;55202;11,70;35902;141949;5,50;89449;404196;6,10;180882;848710;4,60;0;0;2;1316,60;59;24,70;196;7,40;2874;1,20;2866;3,40;17120;2,60;33550;6,30;45110;2,60;498403;2,10;TP 802 5197;; +26048;MARDI;16/06/2026;2;15/09/2026;18;25;37;31;45;4;9;-18-25-31-37-45-;-4-9-;0;0;0;0;0;0;0;7;85884,00;2;22;1612,40;119;496;131,70;302;1410;48,90;242;1095;44,30;4208;19927;12,10;4823;21848;12,30;10843;48177;10,40;23407;108392;5,60;72338;325136;5,90;160349;714942;4,30;0;0;0;0;59;55,00;144;8,00;1998;1,40;2413;3,20;11545;3,10;19812;8,50;35852;2,60;348458;2,40;CK 811 1065;; +26047;VENDREDI;12/06/2026;1;11/09/2026;14;7;22;4;23;7;1;-4-7-14-22-23-;-1-7-;0;0;0;0;3;212362,30;2;13;11453,60;22;77;602,30;418;1533;55,70;756;2696;33,40;685;2686;23,60;8878;32004;9,90;13538;52307;6,70;28848;108670;6,00;36187;136915;5,80;147557;593648;4,20;287961;1209750;3,30;0;0;11;256,30;192;8,10;332;4,70;4220;,90;6508;1,60;17476;2,80;23599;9,60;71515;1,80;434770;2,60;QO 195 2951;; +26046;MARDI;09/06/2026;14;08/09/2026;44;2;23;46;7;5;3;-2-7-23-44-46-;-3-5-;0;1;174984088,00;0;6;148909,60;1;9;23201,70;12;54;1204,40;237;968;123,70;615;2576;49,10;485;2055;43,30;9372;38150;11,60;14040;52650;9,40;25371;100180;9,20;50418;201996;5,50;192517;745660;4,70;362545;1424090;3,90;0;0;3;1330,40;98;22,60;268;8,20;4212;1,30;6436;2,30;22977;3,00;37156;8,70;88909;2,00;670138;2,40;MA 582 0737;; +26045;VENDREDI;05/06/2026;13;04/09/2026;5;6;16;17;49;2;12;-5-6-16-17-49-;-2-12-;0;0;0;0;3;365514,60;2;12;21356,70;3;37;2157,40;313;1096;134,10;562;2181;71,20;715;2475;44,10;9325;33566;16,20;14010;50504;12,00;34358;123783;9,10;47817;175938;7,80;209512;762299;5,60;517606;1873424;3,70;0;0;3;1798,40;152;19,70;304;9,80;4759;1,50;6957;2,80;24424;3,80;38145;11,40;103128;2,30;813539;2,60;PU 143 6071;; +26044;MARDI;02/06/2026;12;01/09/2026;18;9;17;42;6;7;9;-6-9-17-18-42-;-7-9-;0;0;0;1;4;204367,10;2;7;27293,60;17;54;1102,00;316;1132;96,80;890;2764;41,90;582;2032;40,00;11762;39480;10,30;14326;51293;8,80;26856;98632;8,50;54168;186839;5,40;192538;701061;4,60;371190;1381662;3,70;0;0;7;545,10;146;14,50;412;5,10;5438;,90;6650;2,10;25387;2,60;35029;8,80;89187;1,90;623753;2,40;HH 903 3995;; +26043;VENDREDI;29/05/2026;11;28/08/2026;5;14;35;31;18;12;2;-5-14-18-31-35-;-2-12-;0;0;0;3;9;98854,10;3;11;18903,10;13;42;1542,00;229;990;120,50;409;1777;70,90;601;2631;33,60;5790;25593;17,30;9523;41495;11,90;24093;103967;8,80;29784;134217;8,30;136177;595679;5,80;345311;1495645;3,70;0;0;5;729,90;113;17,90;196;10,30;2885;1,70;4568;2,90;15366;4,10;25081;11,80;66195;2,50;558876;2,60;OQ 902 2205;; +26042;MARDI;26/05/2026;10;25/08/2026;23;37;6;35;25;12;6;-6-23-25-35-37-;-6-12-;0;0;0;1;1;624963,40;1;2;73032,10;5;18;2527,50;115;578;144,90;262;1298;68,20;251;1291;48,20;4045;19021;16,30;5388;27104;12,80;13295;66392;9,70;21565;100048;7,80;83455;408086;6,00;204768;1004678;3,90;0;0;2;1148,20;53;24,00;141;9,00;2117;1,50;2551;3,30;10657;3,70;17089;10,90;39857;2,60;348321;2,60;UN 179 1377;; +26041;VENDREDI;22/05/2026;9;21/08/2026;6;31;37;22;26;5;8;-6-22-26-31-37-;-5-8-;0;0;0;1;5;157970,00;2;5;36920,10;12;49;1173,40;161;832;127,30;503;2357;47,50;355;1663;47,30;7731;35864;10,90;8174;38001;11,50;17658;81809;9,90;41160;195050;5,00;120429;568167;5,40;258680;1214421;4,10;1;1670,90;6;222,70;79;21,10;228;7,30;3557;1,10;3861;2,80;18970;2,70;31065;7,80;56868;2,40;495490;2,40;FB 735 2189;; +26040;MARDI;19/05/2026;8;18/08/2026;12;20;45;38;2;5;2;-2-12-20-38-45-;-2-5-;0;0;0;1;3;195442,20;1;9;15226,00;6;23;1855,70;173;640;122,80;354;1623;51,20;367;1365;42,70;5622;24973;11,60;6914;30031;10,80;13151;58287;10,40;27835;128487;5,70;95090;432564;5,30;187448;858994;4,30;1;1246,30;3;332,30;79;15,70;152;8,10;2553;1,20;3297;2,50;12778;3,00;19438;9,30;45251;2,20;377769;2,30;HZ 793 8733;; +26039;VENDREDI;15/05/2026;7;14/08/2026;10;38;43;41;3;2;9;-3-10-38-41-43-;-2-9-;0;0;0;0;1;710654,00;0;2;83045,70;1;27;1916,00;94;568;167,70;316;1577;63,80;262;1324;53,40;4934;25304;13,90;5698;29717;13,20;12529;64558;11,30;29296;143226;6,20;93960;470189;5,90;204094;1015066;4,40;0;0;1;2681,10;48;31,00;153;9,70;2407;1,50;2834;3,50;13781;3,40;24773;8,70;45966;2,60;463094;2,30;NT 565 8161;; +26038;MARDI;12/05/2026;6;11/08/2026;26;35;32;36;4;7;5;-4-26-32-35-36-;-5-7-;0;0;0;0;0;0;0;3;229375,50;3;23;1765,30;77;414;180,60;260;1359;58,10;173;984;56,40;4459;22052;12,50;4246;22838;13,50;8884;47072;12,20;30026;137523;5,00;73784;369066;5,90;147942;748811;4,70;0;0;1;2166,60;43;27,90;139;8,60;2066;1,40;2122;3,80;13801;2,70;26835;6,50;36318;2,70;405239;2,10;VK 398 4678;; +26037;VENDREDI;08/05/2026;5;07/08/2026;19;17;37;34;2;11;8;-2-17-19-34-37-;-8-11-;0;0;0;0;3;233549,00;3;13;12596,30;5;25;2040,10;160;784;119,80;260;1489;66,70;392;1922;36,30;4066;21299;16,30;6330;33335;11,60;16060;80745;8,90;20680;110595;7,90;94407;483556;5,70;236671;1159111;3,80;0;0;2;1298,20;80;18,00;129;11,10;2103;1,70;3164;3,00;10589;4,20;17249;12,20;46867;2,50;396835;2,60;MI 925 5672;; +26036;MARDI;05/05/2026;4;04/08/2026;4;8;3;31;20;6;8;-3-4-8-20-31-;-6-8-;0;0;0;0;4;131785,50;3;6;20533,60;10;43;892,40;214;839;84,20;407;1607;46,50;380;1525;34,40;5274;21603;12,10;7594;31857;9,10;15726;66559;8,10;25290;108130;6,10;95862;421221;4,90;201520;894639;3,70;0;0;6;349,20;94;12,30;202;5,70;2572;1,10;3568;2,10;12334;2,90;18044;9,40;46348;2,00;335557;2,50;TF 464 4375;; +26035;VENDREDI;01/05/2026;3;31/07/2026;42;47;46;3;9;11;1;-3-9-42-46-47-;-1-11-;0;0;0;0;4;171526,90;0;4;40088,60;7;27;1849,80;93;526;174,90;194;1229;79,10;196;1281;53,30;3257;19149;17,80;4369;26222;14,50;10940;62446;11,30;18021;104637;8,20;73933;418568;6,40;192096;1042979;4,10;0;0;3;786,00;53;24,70;97;13,50;1663;1,90;2264;3,80;9348;4,40;15451;12,30;37036;2,90;345262;2,70;UL 104 2892;; +26034;MARDI;28/04/2026;2;28/07/2026;46;47;29;26;41;9;8;-26-29-41-46-47-;-8-9-;0;0;0;0;1;480828,00;1;1;112377,40;1;17;2058,90;66;367;175,60;180;869;78,40;150;845;56,60;2631;13006;18,40;3611;18776;14,20;7689;40046;12,40;15059;73556;8,10;60545;300164;6,30;128435;629888;4,80;0;0;1;1939,10;35;30,70;93;11,50;1367;1,90;1782;4,00;7666;4,40;13904;11,30;30404;2,90;347012;2,20;RB 351 8789;; +26033;VENDREDI;24/04/2026;1;24/07/2026;40;30;45;26;25;5;1;-25-26-30-40-45-;-1-5-;0;0;0;2;3;204255,10;1;8;17901,60;1;17;2623,90;93;570;144,10;275;1329;65,30;231;1242;49,10;3921;19949;15,20;5712;28326;12,00;11172;58532;10,80;22917;108046;7,10;82133;409643;5,90;177462;881728;4,40;0;0;0;0;54;71,00;141;9,70;1855;1,80;2821;3,20;11062;3,90;19350;10,30;41081;2,70;410209;2,40;SG 160 4533;; +26032;MARDI;21/04/2026;12;21/07/2026;29;47;16;13;40;4;3;-13-16-29-40-47-;-3-4-;1;3;48175066,00;2;5;152175,40;2;10;17782,90;7;34;1629,10;177;819;124,50;357;1543;69,90;349;1645;46,00;5339;23601;16,00;8731;37302;11,30;17677;76810;10,20;29541;130852;7,20;130469;555464;5,40;264267;1139139;4,20;1;1766,10;5;282,50;82;21,50;166;10,60;2528;1,70;4037;2,90;13622;4,00;23208;11,10;60207;2,40;557120;2,30;GX 965 7967;; +26031;VENDREDI;17/04/2026;11;17/07/2026;22;23;47;28;41;6;8;-22-23-28-41-47-;-6-8-;0;0;0;0;3;322491,10;4;13;17393,40;10;37;1903,40;158;853;152,00;511;2083;65,80;358;1721;56,00;7900;34393;14,00;8423;41164;13,00;18414;90194;11,00;45977;192949;6,20;142987;663338;5,70;311503;1455435;4,20;0;0;2;1937,60;81;26,50;249;8,60;3636;1,40;4080;3,50;21500;3,10;34715;9,00;68384;2,50;647791;2,40;TE 349 5398;; +26030;MARDI;14/04/2026;10;14/07/2026;1;2;4;44;28;12;5;-1-2-4-28-44-;-5-12-;0;0;0;0;2;344056,40;1;2;80411,60;3;37;1353,80;122;662;139,30;293;1408;69,20;311;1597;42,90;4045;20342;16,80;6474;32379;11,80;17191;80566;8,80;22116;109721;7,80;96235;466267;5,80;240375;1122055;3,80;0;0;2;1276,30;65;21,80;144;9,80;2061;1,70;3176;2,90;11013;4,00;17397;11,90;46853;2,40;389630;2,60;HO 590 8240;; +26029;VENDREDI;10/04/2026;9;10/07/2026;10;13;41;38;14;6;9;-10-13-14-38-41-;-6-9-;0;0;0;0;3;270959,60;1;3;63327,70;10;42;1408,90;192;860;126,70;433;2122;54,30;406;1767;45,80;7531;32793;12,30;8323;37799;11,90;18104;82775;10,10;40001;176245;5,70;136943;587746;5,40;290697;1280340;4,00;0;0;4;832,30;77;24,00;201;9,20;3568;1,20;3979;3,10;18950;3,00;29575;9,10;64633;2,30;543801;2,40;LY 091 0623;; +26028;MARDI;07/04/2026;8;07/07/2026;14;49;11;19;36;7;6;-11-14-19-36-49-;-6-7-;0;0;0;2;5;113717,30;0;3;44296,00;5;33;1254,20;137;617;123,50;277;1342;60,00;249;1242;45,60;4298;20355;13,90;6328;28764;10,90;11637;56719;10,30;23220;107960;6,50;97571;435910;5,10;182568;864402;4,10;0;0;3;720,80;68;17,60;135;8,80;2022;1,40;3023;2,60;10943;3,40;17579;9,90;46008;2,10;390606;2,20;JH 669 2859;; +26027;VENDREDI;03/04/2026;7;03/07/2026;27;46;8;49;29;10;2;-8-27-29-46-49-;-2-10-;0;0;0;1;3;236718,40;1;3;55324,90;5;42;1230,80;138;617;154,30;369;1583;63,50;337;1462;48,30;5087;22828;15,40;6755;30417;12,90;15920;72157;10,10;27744;120579;7,30;103362;459870;6,00;245680;1100234;4,10;0;0;2;1468,40;60;27,10;175;9,30;2685;1,50;3324;3,20;14303;3,50;24117;9,80;51680;2,60;466773;2,50;SU 524 4508;; +26026;MARDI;31/03/2026;6;30/06/2026;8;38;10;5;33;2;7;-5-8-10-33-38-;-2-7-;0;0;0;1;3;189523,00;0;6;22147,30;10;42;985,40;123;573;133,00;451;1818;44,30;271;1335;42,40;6606;27033;10,40;6486;30287;10,40;12934;63580;9,20;33650;140223;5,00;98453;454155;4,90;190417;924494;3,90;0;0;4;537,50;58;20,50;210;5,60;3083;,90;3092;2,50;15546;2,40;23511;7,40;46759;2,10;366046;2,30;CM 690 8200;; +26025;VENDREDI;27/03/2026;5;26/06/2026;44;4;43;10;48;4;2;-4-10-43-44-48-;-2-4-;0;0;0;1;2;353647,20;3;6;27551,00;4;31;1660,90;115;605;156,70;302;1398;71,70;233;1172;60,10;5135;23682;14,80;5965;29474;13,30;12068;60402;12,10;29558;133485;6,60;104059;478401;5,80;208023;990321;4,50;1;1573,70;2;629,40;80;19,60;153;10,20;2512;1,50;2937;3,50;13956;3,50;22753;10,00;51300;2,50;475250;2,40;LL 648 3204;; +26024;MARDI;24/03/2026;4;23/06/2026;18;27;16;12;17;3;1;-12-16-17-18-27-;-1-3-;0;0;0;2;3;172870,70;0;9;13467,50;4;29;1301,80;193;804;86,40;346;1602;45,80;379;1645;31,40;4605;20456;12,60;7733;31703;9,00;16553;70112;7,60;21286;95948;6,70;101412;433402;4,70;218629;950092;3,40;0;0;2;1049,70;91;12,80;157;7,40;2172;1,30;3609;2,10;10107;3,60;14546;11,70;48254;1,90;321215;2,60;NJ 917 9927;; +26023;VENDREDI;20/03/2026;3;19/06/2026;16;12;5;37;46;8;10;-5-12-16-37-46-;-8-10-;0;0;0;1;3;220584,60;1;3;51554,20;5;20;2408,60;126;659;134,60;288;1314;71,30;309;1422;46,30;4404;19313;17,00;6681;29512;12,40;15985;69774;9,80;22855;101943;8,10;98674;436798;5,90;241970;1048414;4,00;1;1535,20;2;614,10;70;21,90;155;9,90;2320;1,60;3413;3,00;12125;3,90;20058;11,10;49809;2,50;430923;2,50;NG 772 7907;; +26022;MARDI;17/03/2026;2;16/06/2026;28;33;41;17;5;9;3;-5-17-28-33-41-;-3-9-;0;0;0;2;12;42063,70;6;16;7373,20;13;52;706,60;174;842;80,30;463;1906;37,50;373;1621;31,00;5569;25081;10,00;6503;30247;9,20;13353;59922;8,70;27951;125629;5,00;87155;395936;5,00;183067;813883;3,90;0;0;7;304,50;75;15,70;219;5,40;2671;1,10;3039;2,60;13681;2,70;22636;7,60;42640;2,20;358302;2,40;JI 632 6165;; +26021;VENDREDI;13/03/2026;1;12/06/2026;26;17;48;41;13;10;4;-13-17-26-41-48-;-4-10-;0;0;0;0;2;415147,60;1;5;38810,70;17;54;1119,30;231;867;128,40;532;1980;59,40;537;2202;37,50;7491;27769;14,80;9815;39875;11,50;24348;97919;8,70;38293;142691;7,20;146302;569073;5,70;358932;1399692;3,70;0;0;9;463,80;119;19,40;270;8,50;3881;1,40;4967;3,10;19992;3,60;32813;10,30;75584;2,50;639512;2,60;IZ 061 2918;; +26020;MARDI;10/03/2026;11;09/06/2026;27;12;50;14;44;4;12;-12-14-27-44-50-;-4-12-;0;1;209527211,00;1;2;516689,70;2;10;24151,70;17;52;1446,60;316;1125;123,10;607;2306;63,50;719;2598;39,60;8513;32452;15,80;13190;48880;11,70;32560;119072;8,90;42716;163108;7,90;188222;704669;5,70;475361;1744382;3,70;0;0;5;967,70;126;21,30;277;9,70;4065;1,60;6267;2,80;20580;4,10;32328;12,10;87815;2,50;719521;2,70;MM 280 8208;; +26019;VENDREDI;06/03/2026;10;05/06/2026;16;19;15;28;37;9;6;-15-16-19-28-37-;-6-9-;0;0;0;2;5;233193,40;3;9;30278,40;16;75;1131,70;386;1355;115,30;932;3475;47,50;818;2990;38,80;12783;48703;11,90;17577;62853;10,30;37062;135577;8,80;66772;251189;5,80;237677;870310;5,20;504535;1887204;3,90;1;3102,30;3;827,20;179;17,30;412;7,50;6007;1,20;8267;2,50;31319;3,10;49110;9,20;111911;2,20;916138;2,40;CZ 541 3784;; +26018;MARDI;03/03/2026;9;02/06/2026;50;24;6;34;7;5;7;-6-7-24-34-50-;-5-7-;0;0;0;0;6;147207,10;0;5;41285,60;5;46;1397,70;213;915;129,40;621;2592;48,30;378;1744;50,40;10337;41288;10,60;10253;43355;11,30;20318;86078;10,60;57813;225691;4,90;166899;677964;5,10;329297;1342534;4,10;0;0;3;1300,70;120;18,00;302;7,10;4768;1,10;4857;2,90;26138;2,60;40316;7,80;78301;2,20;667959;2,30;;; +26017;VENDREDI;27/02/2026;8;29/05/2026;27;24;39;42;14;10;6;-14-24-27-39-42-;-6-10-;0;0;0;5;14;75686,50;9;31;7988,60;23;76;1014,90;464;1610;88,20;683;2710;55,40;914;3615;29,10;8952;36233;14,50;14241;54853;10,70;32230;130052;8,40;45222;185628;7,10;187989;734167;5,60;432187;1755815;3,80;2;1313,40;7;300,20;207;12,60;330;7,90;4404;1,40;6676;2,60;22062;3,70;35829;10,70;89730;2,40;745452;2,50;LQ 197 7609;; +26016;MARDI;24/02/2026;7;26/05/2026;10;43;47;40;27;10;6;-10-27-40-43-47-;-6-10-;0;0;0;1;4;199961,60;3;6;31156,20;10;40;1455,60;167;772;138,90;376;1764;64,20;349;1740;45,70;5786;25330;15,70;7729;33171;13,30;17452;77066;10,70;32497;136052;7,30;119361;505784;6,20;273448;1178407;4,30;0;0;4;845,10;77;24,30;199;9,40;2856;1,60;3731;3,30;15458;3,80;25977;10,50;57203;2,70;550785;2,40;HG 063 7125;; +26015;VENDREDI;20/02/2026;6;22/05/2026;24;33;28;35;13;5;9;-13-24-28-33-35-;-5-9-;0;0;0;1;7;161198,00;4;10;26372,20;12;66;1244,50;337;1412;107,10;939;3804;42,00;728;2881;39,00;12913;52618;10,60;16005;62730;9,90;35179;129858;8,90;65149;261719;5,40;227794;877573;5,00;499846;1834281;3,90;1;3022,60;4;604,50;165;18,30;397;7,60;5958;1,20;7577;2,60;30162;3,10;48440;9,10;107517;2,30;884539;2,40;UR 586 8252;; +26014;MARDI;17/02/2026;5;19/05/2026;6;41;1;10;4;12;5;-1-4-6-10-41-;-5-12-;0;0;0;1;5;109156,50;0;4;31889,60;6;30;1324,30;161;664;110,20;262;1087;71,10;453;1653;32,80;3447;15352;17,70;6377;26733;11,30;16799;67841;8,30;17540;81765;8,30;84799;372429;5,70;216295;905276;3,80;1;1251,80;5;200,20;91;13,70;129;9,70;1841;1,60;3135;2,60;9303;4,20;15081;12,10;42133;2,40;343181;2,60;NR 762 9710;; +26013;VENDREDI;13/02/2026;4;15/05/2026;37;40;13;31;9;6;9;-9-13-31-37-40-;-6-9-;0;0;0;1;3;263509,10;2;7;26394,10;13;55;1046,30;238;837;126,60;642;2259;49,60;468;1771;44,40;9416;33340;11,80;11244;39671;11,00;23625;83918;9,70;48579;171434;5,70;168204;579594;5,30;354968;1231716;4,00;1;2382,60;8;238,20;132;18,00;298;7,90;4549;1,30;5767;2,70;23638;3,10;35923;9,60;82894;2,30;699833;2,40;PR 479 0473;; +26012;MARDI;10/02/2026;3;12/05/2026;1;19;42;17;34;8;5;-1-17-19-34-42-;-5-8-;0;0;0;0;0;0;1;6;102136,10;10;39;927,10;113;551;120,80;343;1631;43,10;285;1144;43,20;5320;23940;10,30;5727;25601;10,70;13856;56574;9,00;27425;127436;4,80;81408;370210;5,20;184475;797590;3,90;0;0;5;409,70;69;16,40;166;6,80;2523;1,10;2828;2,60;12797;2,80;21354;7,70;39825;2,30;332291;2,40;TO 505 2133;; +26011;VENDREDI;06/02/2026;2;08/05/2026;20;10;24;13;23;6;11;-10-13-20-23-24-;-6-11-;0;0;0;1;5;126931,60;3;14;10595,00;9;37;1248,60;269;984;86,40;371;1623;55,40;692;2662;23,70;5357;22579;14,00;9265;38119;9,20;25435;100007;6,50;25494;110407;7,20;118356;499088;5,00;304506;1258739;3,20;1;1497,40;5;239,50;126;11,80;193;7,70;2726;1,30;4514;2,20;12972;3,60;19415;11,20;57537;2,10;395381;2,70;EF 203 4608;; +26010;MARDI;03/02/2026;1;05/05/2026;27;28;37;34;26;4;9;-26-27-28-34-37-;-4-9-;0;0;0;0;1;471469,20;0;7;15741,40;3;18;1906,70;82;388;162,90;232;1151;58,00;202;877;53,50;3970;19584;11,90;4021;18939;13,80;9036;41789;11,60;23526;111331;5,30;67441;312092;5,90;152745;690122;4,30;0;0;1;1981,80;45;24,40;111;9,90;1872;1,40;2004;3,60;11169;3,10;18095;8,80;33271;2,70;320221;2,40;NO 936 5502;; +26009;VENDREDI;30/01/2026;10;01/05/2026;14;18;31;35;46;7;11;-14-18-31-35-46-;-7-11-;0;1;123555827,00;0;3;289086,80;2;7;28956,10;12;50;1262,60;170;854;136,10;430;1892;64,90;430;1874;46,10;6164;28458;15,10;8405;38283;12,50;18299;83970;10,60;33871;155850;6,90;129870;575481;5,90;280172;1257673;4,30;0;0;8;443,40;74;26,60;210;9,30;3005;1,60;3983;3,30;16435;3,70;29091;9,80;61727;2,60;610913;2,30;HV 221 1285;; +26008;MARDI;27/01/2026;9;28/04/2026;47;42;23;43;4;9;3;-4-23-42-43-47-;-3-9-;0;0;0;0;0;0;1;8;95070,10;5;24;1869,90;106;574;144,00;314;1638;53,30;195;1092;56,20;5566;26682;11,50;5244;26850;12,70;11401;56729;11,20;31265;142515;5,40;91858;441567;5,50;184591;882221;4,40;0;0;2;1189,70;52;25,40;146;9,00;2551;1,20;2521;3,50;14399;2,80;25222;7,60;43521;2,50;408859;2,30;CY 698 4086;; +26007;VENDREDI;23/01/2026;8;24/04/2026;4;42;5;13;21;3;10;-4-5-13-21-42-;-3-10-;0;0;0;0;2;398883,80;0;6;31075,20;11;60;967,90;241;1034;103,40;559;2305;49,00;592;2531;31,30;6890;30529;13,00;10497;47096;9,40;23988;107828;7,60;32433;151415;6,60;142333;644533;4,80;308576;1429692;3,50;0;0;2;1475,60;109;15,00;257;6,30;3359;1,20;4835;2,20;15891;3,20;23819;10,00;67817;1,90;451373;2,60;KC 826 3307;; +26006;MARDI;20/01/2026;7;21/04/2026;22;18;19;50;11;1;11;-11-18-19-22-50-;-1-11-;0;0;0;0;2;295401,40;1;3;46026,70;9;40;1075,20;101;615;128,80;260;1406;59,50;310;1468;40,00;3946;19497;15,00;6078;28948;11,30;15513;71902;8,50;19212;95225;7,70;87465;412376;5,60;230267;1058593;3,50;0;0;5;452,40;49;25,60;132;9,50;1975;1,50;2968;2,80;9602;4,10;14240;12,80;42251;2,40;314729;2,90;FR 631 6934;; +26005;VENDREDI;16/01/2026;6;17/04/2026;5;24;17;50;29;10;5;-5-17-24-29-50-;-5-10-;0;0;0;0;3;265077,70;3;16;11616,10;9;52;1113,20;203;1028;103,70;384;2143;52,60;446;2257;35,00;5717;29909;13,20;8613;43718;10,10;19748;97505;8,40;27731;148081;6,70;118961;606740;5,10;270885;1353642;3,70;0;0;2;1416,50;100;15,70;201;7,80;2844;1,30;4054;2,60;14103;3,50;20776;11,00;57765;2,20;432549;2,60;RC 732 6307;; +26004;MARDI;13/01/2026;5;14/04/2026;47;6;44;10;18;10;2;-6-10-18-44-47-;-2-10-;0;0;0;0;3;187049,90;2;6;21858,30;7;22;1856,80;127;573;131,30;333;1292;61,50;310;1350;41,40;4531;19414;14,30;5742;25611;12,10;13094;59191;9,80;21994;99749;7,00;85460;383361;5,70;194658;894698;3,90;0;0;2;1090,40;56;21,60;174;6,90;2316;1,30;2786;2,90;11079;3,40;17457;10,10;42077;2,30;337610;2,60;IB 283 6404;; +26003;VENDREDI;09/01/2026;4;10/04/2026;10;34;26;7;1;2;4;-1-7-10-26-34-;-2-4-;0;0;0;2;6;116285,00;3;9;18118,40;7;45;1128,60;241;928;100,80;445;1945;50,80;515;2090;33,20;6055;26684;13,00;9650;40232;9,60;20474;88601;8,10;28906;133462;6,50;124067;550548;5,00;266193;1194409;3,70;0;0;2;1357,90;96;15,70;185;8,10;2759;1,30;4541;2,20;13447;3,50;20744;10,60;58523;2,10;435263;2,50;GZ 103 4529;; +26002;MARDI;06/01/2026;3;07/04/2026;5;17;18;14;31;12;10;-5-14-17-18-31-;-10-12-;0;0;0;0;0;0;1;3;204841,20;4;22;1648,20;129;540;123,60;204;1007;70,10;418;1693;29,30;2943;13573;18,20;5315;22783;12,10;17053;71070;7,20;14943;68508;9,10;72116;313379;6,20;224029;962787;3,20;0;0;2;1025,60;65;17,50;109;10,40;1579;1,80;2730;2,70;7906;4,50;12428;13,30;37203;2,50;280074;2,90;EB 277 2217;; +26001;VENDREDI;02/01/2026;2;03/04/2026;46;42;27;44;8;10;1;-8-27-42-44-46-;-1-10-;0;0;0;0;4;181127,30;2;9;18814,40;8;37;1425,40;124;619;156,90;262;1344;76,40;283;1563;46,10;3783;19962;18,00;5585;29286;13,70;15260;76932;9,70;20750;107521;8,40;84500;435736;6,50;229238;1161243;3,90;0;0;3;886,50;69;21,40;139;10,60;1988;1,80;2813;3,50;10948;4,20;18224;11,80;42763;2,80;400288;2,60;QI 521 4040;; +25104;MARDI;30/12/2025;1;31/03/2026;29;44;26;11;34;10;1;-11-26-29-34-44-;-1-10-;0;0;0;0;1;539027,50;2;6;20996,60;3;22;1783,60;104;493;146,60;221;962;79,40;283;1405;38,20;3072;14686;18,20;4847;23190;12,90;13138;63325;8,80;16409;77735;8,60;71120;337455;6,30;196775;918795;3,70;0;0;1;2168,00;50;24,00;115;10,40;1692;1,70;2494;3,20;8737;4,30;14471;12,10;36392;2,70;321784;2,70;HR 329 6221;; +25103;VENDREDI;26/12/2025;4;27/03/2026;22;32;48;12;36;3;4;-12-22-32-36-48-;-3-4-;0;1;54026007,00;2;9;73045,20;2;8;19205,80;6;23;2080,70;172;801;110,00;309;1407;66,20;309;1654;39,50;4142;19713;16,60;6897;32786;11,10;14146;66857;10,10;23877;110345;7,40;102259;463404;5,50;205442;939287;4,40;0;0;5;540,70;91;16,50;146;10,20;2013;1,80;3347;3,00;11457;4,10;19308;11,30;48782;2,50;480598;2,20;SF 344 7031;; +25102;MARDI;23/12/2025;3;24/03/2026;26;29;44;8;27;11;12;-8-26-27-29-44-;-11-12-;0;0;0;2;6;99593,30;2;8;17457,40;3;22;1977,20;119;614;130,50;186;1046;80,90;318;1459;40,70;2887;15398;19,30;5403;27189;12,20;16495;75938;8,10;15062;81923;9,10;76310;382289;6,10;232481;1073472;3,50;1;1290,70;2;516,20;60;21,50;100;12,90;1555;2,00;2858;3,00;8208;4,90;13601;13,80;39561;2,60;319282;2,90;QK 002 7273;; +25101;VENDREDI;19/12/2025;2;20/03/2026;39;43;44;21;17;1;11;-17-21-39-43-44-;-1-11-;0;0;0;1;2;338895,20;1;6;26401,80;1;17;2902,40;118;647;140,40;236;1197;80,20;333;1589;42,40;3732;18490;18,20;5580;28182;13,30;14753;72337;9,60;20633;101590;8,30;87025;417169;6,40;235820;1095056;3,90;1;1561,60;0;0;52;54,00;121;12,90;2008;1,90;2906;3,60;10945;4,40;18701;12,10;44627;2,80;419166;2,70;MR 927 8268;; +25100;MARDI;16/12/2025;1;17/03/2026;41;14;16;44;40;2;10;-14-16-40-41-44-;-2-10-;0;0;0;1;1;488880,90;3;7;16322,70;4;16;2224,30;80;418;156,80;206;979;70,70;156;925;52,60;2873;14388;16,90;3666;18645;14,50;8531;44138;11,40;17982;81894;7,40;60081;293965;6,50;140198;693990;4,40;1;1113,90;2;445,50;41;27,10;122;9,10;1534;1,80;1908;3,90;9343;3,70;16350;9,90;31051;2,90;330996;2,40;SB 120 7415;; +25099;VENDREDI;12/12/2025;1;13/03/2026;30;7;25;41;37;11;5;-7-25-30-37-41-;-5-11-;0;1;17000000,00;1;4;164437,20;1;5;30745,30;5;26;1841,60;127;611;144,30;294;1447;64,40;302;1460;44,80;4561;21430;15,20;6471;30341;12,00;15419;68638;9,90;24090;115000;7,10;101530;457820;5,60;242534;1031927;4,00;0;0;4;729,40;67;24,10;154;10,50;2384;1,60;3234;3,30;12531;4,00;20752;11,40;51167;2,60;455579;2,50;QP 012 5788;; +25098;MARDI;09/12/2025;3;10/03/2026;13;49;2;29;8;2;11;-2-8-13-29-49-;-2-11-;0;1;142433804,00;1;7;110381,70;5;11;16416,90;15;46;1222,70;240;971;106,70;507;1982;55,20;558;2143;35,90;7221;28586;13,40;10771;41793;10,20;24447;93471;8,50;35592;143957;6,70;142535;571700;5,30;333400;1301047;3,70;1;1857,40;6;247,60;119;15,60;242;7,60;3424;1,30;5044;2,40;16714;3,50;25742;10,50;66002;2,30;505973;2,60;HV 507 3990;; +25097;VENDREDI;05/12/2025;2;06/03/2026;34;25;15;46;9;8;12;-9-15-25-34-46-;-8-12-;0;0;0;1;2;548975,60;3;14;18329,20;12;48;1665,10;267;1097;134,20;530;2013;77,30;715;2755;39,70;7776;30712;17,80;11887;48247;12,60;29829;119175;9,50;41564;164376;8,30;182315;712359;6,00;460232;1772798;3,90;1;2897,80;6;386,30;144;20,10;264;10,90;3991;1,80;5880;3,30;21240;4,20;35528;11,90;89103;2,60;810422;2,50;NQ 393 9174;; +25096;MARDI;02/12/2025;1;03/03/2026;14;4;41;13;20;6;12;-4-13-14-20-41-;-6-12-;0;0;0;0;2;249653,50;1;9;12966,20;8;23;1580,30;104;467;143,30;228;1004;70,50;286;1141;43,50;3429;14715;16,90;5063;21976;12,60;13223;58195;8,80;18294;78527;7,90;73102;326849;6,00;193521;860109;3,60;0;0;3;696,30;56;20,70;118;9,80;1884;1,50;2582;3,00;9875;3,70;15884;10,60;37243;2,50;311819;2,70;SB 684 6490;; +25095;VENDREDI;28/11/2025;14;27/02/2026;5;33;42;29;39;3;9;-5-29-33-39-42-;-3-9-;1;1;178656733,00;1;5;205348,80;0;6;39994,50;11;60;1245,70;230;1054;130,60;634;2987;48,70;471;2222;46,00;9733;46143;11,00;10045;49711;11,40;21435;102915;10,30;53199;243240;5,20;158225;749530;5,40;322957;1533526;4,20;0;0;3;1304,10;112;19,40;312;6,90;4449;1,20;4760;3,00;24359;2,80;40276;7,80;74702;2,40;659359;2,30;DT 593 0475;; +25094;MARDI;25/11/2025;13;24/02/2026;11;35;44;17;6;7;3;-6-11-17-35-44-;-3-7-;0;0;0;0;0;0;5;13;74451,30;19;73;782,30;198;985;106,80;665;3109;35,70;449;1979;39,40;10226;44655;8,70;9880;44650;9,70;18965;88922;9,10;53432;231707;4,20;137234;624136;4,90;261958;1240535;4,00;0;0;10;289,50;96;16,70;279;5,70;4439;,90;4413;2,40;22908;2,20;36149;6,40;62023;2,10;500048;2,30;LI 694 8496;; +25093;VENDREDI;21/11/2025;12;20/02/2026;35;17;48;29;19;9;5;-17-19-29-35-48-;-5-9-;0;0;0;1;5;187734,70;3;9;24375,90;16;67;1019,80;173;1034;121,70;544;2862;46,40;482;2348;39,80;8427;42501;11,00;10260;49206;10,50;21330;99866;9,70;44930;221576;5,30;149318;703948;5,20;319402;1454386;4,10;0;0;7;514,30;73;27,40;232;8,60;3710;1,30;4733;2,80;20202;3,10;32637;8,90;69569;2,30;597530;2,40;MF 112 7235;; +25092;MARDI;18/11/2025;11;17/02/2026;48;4;2;21;15;12;6;-2-4-15-21-48-;-6-12-;0;0;0;2;6;122146,50;2;8;21410,70;8;35;1524,30;179;744;132,00;309;1488;69,80;457;1940;37,60;4888;22269;16,30;7255;33187;12,20;18614;83955;9,00;26308;115687;7,90;105990;479072;6,00;268909;1219556;3,80;1;1570,60;4;314,10;73;21,50;152;10,30;2487;1,50;3419;3,00;13025;3,70;21964;10,40;50473;2,50;429657;2,60;PI 805 4876;; +25091;VENDREDI;14/11/2025;10;13/02/2026;9;45;26;48;27;9;8;-9-26-27-45-48-;-8-9-;0;0;0;1;3;294047,90;0;4;51542,80;8;41;1566,20;141;852;138,80;381;1949;64,10;335;1706;51,50;5701;27927;15,70;8823;43975;11,10;17521;88029;10,30;29359;145774;7,50;133302;650475;5,30;271490;1317488;4,20;1;1833,90;3;489,00;59;31,00;198;9,20;2872;1,50;4157;2,90;14308;4,00;22368;11,90;63427;2,30;549951;2,40;FF 071 6932;; +25090;MARDI;11/11/2025;9;10/02/2026;47;22;32;36;4;2;10;-4-22-32-36-47-;-2-10-;0;0;0;1;2;322798,60;2;4;37721,60;3;31;1516,00;113;593;145,90;231;1351;67,70;239;1353;47,50;3587;19910;16,10;4600;26686;13,40;10668;62698;10,60;20853;108725;7,40;71688;400105;6,30;169154;944285;4,30;0;0;3;730,30;62;19,60;114;10,60;1674;1,80;2237;3,60;10190;3,70;18141;9,70;34520;2,90;362932;2,40;UL 250 5542;; +25089;VENDREDI;07/11/2025;8;06/02/2026;40;21;43;11;39;8;2;-11-21-39-40-43-;-2-8-;0;0;0;0;4;199745,20;1;7;26676,40;8;30;1938,70;121;695;154,10;368;1983;57,10;271;1459;54,50;6549;31389;12,60;6400;34361;12,90;14117;76186;10,80;39043;181670;5,50;102922;526727;5,90;227806;1164335;4,30;0;0;5;602,90;64;26,10;191;8,70;2956;1,40;3173;3,50;18016;2,90;32150;7,60;50158;2,70;508165;2,30;BU 251 5247;; +25088;MARDI;04/11/2025;7;03/02/2026;25;28;45;9;6;1;4;-6-9-25-28-45-;-1-4-;0;0;0;0;3;197503,10;0;8;17309,90;6;29;1487,30;170;711;111,70;294;1404;59,80;406;1759;33,50;4124;19536;15,10;6546;30878;10,60;16130;74726;8,20;19806;98351;7,50;90743;431470;5,40;214763;1023577;3,60;0;0;2;1087,80;79;15,20;135;8,90;1915;1,50;3033;2,60;9359;4,00;15022;11,70;43357;2,20;331957;2,60;RJ 760 6571;; +25087;VENDREDI;31/10/2025;6;30/01/2026;43;38;5;45;14;7;11;-5-14-38-43-45-;-7-11-;0;0;0;0;2;405711,30;4;6;31607,10;9;41;1440,70;136;704;154,50;309;1646;69,80;285;1545;52,30;4941;26385;15,30;6700;34264;13,10;14429;75210;11,10;27982;148030;6,80;106265;528099;6,00;229681;1165262;4,40;0;0;4;753,50;62;27,00;172;9,70;2510;1,60;3312;3,30;14055;3,70;24453;9,90;52382;2,60;517235;2,30;CU 609 8974;; +25086;MARDI;28/10/2025;5;27/01/2026;35;49;24;7;8;2;12;-7-8-24-35-49-;-2-12-;0;0;0;1;1;540419,60;3;9;14033,80;10;22;1788,20;104;533;135,90;226;1051;72,80;259;1321;40,70;3030;15057;17,80;4905;24554;12,20;12492;61526;9,00;16165;79662;8,40;76124;363956;5,80;192928;917369;3,70;0;0;3;683,90;65;17,50;126;9,00;1563;1,80;2435;3,10;8530;4,20;13104;12,70;37628;2,40;307235;2,60;KT 230 2700;; +25085;VENDREDI;24/10/2025;4;23/01/2026;25;8;45;7;31;12;7;-7-8-25-31-45-;-7-12-;0;0;0;0;2;343025,20;4;12;13361,70;11;32;1560,60;203;854;107,70;350;1430;68,00;404;1784;38,30;4724;21481;15,90;8240;36614;10,40;17432;80170;8,80;23491;109256;7,80;115960;521011;5,10;254605;1166133;3,70;0;0;7;382,60;94;15,80;192;7,70;2490;1,40;3933;2,50;12226;3,80;18133;11,90;56081;2,10;418938;2,50;HE 121 1375;; +25084;MARDI;21/10/2025;3;20/01/2026;5;29;40;24;42;6;12;-5-24-29-40-42-;-6-12-;0;0;0;0;0;0;0;4;155906,70;3;16;2299,80;82;443;153,00;218;977;73,30;225;1097;45,90;3181;14683;17,10;4378;20790;13,50;11129;53264;9,80;16880;77393;8,10;68270;317298;6,20;173392;812936;3,90;0;0;1;1979,80;39;28,20;115;9,50;1656;1,60;2214;3,30;8871;3,90;14405;11,10;34219;2,60;294975;2,70;OA 155 5726;; +25083;VENDREDI;17/10/2025;2;16/01/2026;39;44;35;47;13;3;5;-13-35-39-44-47-;-3-5-;0;0;0;0;1;647213,60;0;6;25210,70;9;37;1273,30;134;635;136,60;317;1585;57,80;227;1242;51,90;5255;25880;12,40;6194;30386;11,80;12530;60552;11,00;30733;146767;5,50;89803;444580;5,70;184580;888164;4,60;0;0;6;437,70;64;22,70;157;9,20;2587;1,40;3053;3,20;14352;3,20;27657;7,70;44593;2,60;476460;2,20;HD 452 5951;; +25082;MARDI;14/10/2025;1;13/01/2026;8;5;18;16;14;10;3;-5-8-14-16-18-;-3-10-;0;0;0;1;2;234121,90;2;8;13679,50;6;26;1311,00;201;708;88,60;338;1318;50,30;474;1702;27,40;4192;17604;13,20;6863;27890;9,30;16711;67762;7,10;20445;87453;6,70;86770;372233;4,90;205398;876026;3,30;0;0;6;323,30;90;11,90;179;6,00;2148;1,20;3384;2,10;10221;3,30;14856;10,50;42620;2,00;296229;2,60;MJ 556 9345;; +25081;VENDREDI;10/10/2025;2;09/01/2026;7;21;20;17;6;10;1;-6-7-17-20-21-;-1-10-;0;1;29671390,00;3;11;60131,50;4;13;11891,60;3;37;1301,30;218;897;98,80;416;1656;56,60;675;2549;25,80;4965;21288;15,40;9782;38649;9,50;25960;107097;6,30;22718;100891;8,20;119180;501601;5,20;319152;1360688;3,00;1;1526,50;1;1221,20;95;16,00;206;7,40;2547;1,40;4609;2,20;11742;4,00;17409;12,80;58031;2,10;380273;2,90;MX 457 8544;; +25080;MARDI;07/10/2025;1;06/01/2026;24;39;48;43;42;5;8;-24-39-42-43-48-;-5-8-;0;0;0;0;2;241122,50;0;3;37569,50;7;22;1595,70;93;373;173,30;232;1213;56,30;178;851;56,40;3839;19086;12,50;3761;18910;14,10;8359;40481;12,30;23479;112362;5,30;60177;294871;6,40;130786;623926;4,90;0;0;5;401,70;57;19,50;126;8,80;1960;1,40;1988;3,70;11587;3,00;22682;7,10;31252;2,90;358754;2,20;GE 840 8288;; +25079;VENDREDI;03/10/2025;2;02/01/2026;25;41;6;12;18;2;6;-6-12-18-25-41-;-2-6-;0;1;29922038,00;0;1;674530,30;3;8;19706,10;24;67;732,80;234;985;91,80;590;2427;39,30;437;1952;34,40;7923;31531;10,60;9781;40937;9,10;19317;81557;8,50;35855;149056;5,60;128145;531787;5,00;256411;1109294;3,80;0;0;9;313,20;111;14,10;273;5,70;3636;1,00;4779;2,10;16762;2,90;24438;9,30;62401;2,00;457442;2,40;NV 550 7879;; +25078;MARDI;30/09/2025;1;30/12/2025;48;15;8;3;17;2;8;-3-8-15-17-48-;-2-8-;0;0;0;1;8;62213,20;0;2;58161,00;7;47;770,80;156;645;103,40;402;1907;36,90;295;1243;39,80;5649;26244;9,40;6684;29414;9,40;13874;59939;8,50;28453;127594;4,80;89521;406247;4,80;186811;848336;3,70;1;1141,40;3;304,30;86;13,20;174;6,50;2762;1,00;3327;2,20;13757;2,60;20107;8,20;44404;2,10;324942;2,50;HX 965 5996;; +25077;VENDREDI;26/09/2025;6;26/12/2025;25;17;4;44;28;11;5;-4-17-25-28-44-;-5-11-;0;1;130000000,00;5;12;95952,40;1;17;15829,80;22;94;891,70;455;1708;90,40;738;3013;54,10;1113;4004;28,60;9885;41525;13,80;17089;66930;9,50;41911;154860;7,60;47901;207067;6,90;215459;869935;5,20;525160;1999731;3,60;4;733,90;10;234,80;209;14,00;377;7,70;4798;1,50;8041;2,40;23607;3,90;37831;11,30;102905;2,30;808839;2,60;IQ 493 4176;; +25076;MARDI;23/09/2025;5;23/12/2025;13;29;33;11;24;5;2;-11-13-24-29-33-;-2-5-;0;0;0;0;1;565440,80;1;4;33038,20;9;64;643,10;186;884;85,70;455;2140;37,40;414;1861;30,20;5964;27386;10,20;8980;40618;7,70;17768;80304;7,20;27530;127952;5,50;109481;496268;4,40;226396;1015359;3,50;0;0;6;374,50;98;12,70;199;6,20;2721;1,10;4138;2,00;12803;3,00;19390;9,40;52043;1,90;361528;2,50;IW 582 7095;; +25075;VENDREDI;19/09/2025;4;19/12/2025;26;32;42;8;10;9;12;-8-10-26-32-42-;-9-12-;0;0;0;0;1;700266,50;2;5;32732,70;1;36;1416,00;121;677;138,70;271;1383;71,70;342;1615;43,10;3727;19071;18,20;6087;30632;12,70;16011;77812;9,30;19766;100763;8,70;93776;461345;5,90;249728;1168223;3,80;0;0;1;2685,20;67;22,20;131;11,30;1898;1,90;3038;3,20;10299;4,50;17161;12,60;46769;2,60;407244;2,60;UP 609 9974;; +25074;MARDI;16/09/2025;3;16/12/2025;35;9;13;1;40;6;5;-1-9-13-35-40-;-5-6-;0;0;0;1;2;265444,10;0;3;41359,10;5;26;1486,40;112;542;131,30;228;1210;62,10;260;1232;42,90;3649;18150;14,50;5921;27066;10,80;12247;55780;9,80;19478;96689;6,80;87812;404814;5,10;178623;824639;4,00;1;1181,80;2;472,70;62;19,00;116;10,10;1834;1,60;2931;2,70;9594;3,80;15314;11,20;42934;2,20;364306;2,30;TW 640 9289;; +25073;VENDREDI;12/09/2025;2;12/12/2025;10;37;31;5;23;3;11;-5-10-23-31-37-;-3-11-;0;0;0;1;2;333007,50;1;5;31131,70;8;49;989,40;164;761;117,30;356;1674;56,40;447;1911;34,70;4940;23276;14,20;8254;36368;10,10;19189;81573;8,40;25431;118433;7,00;112101;502978;5,20;263821;1136356;3,70;0;0;3;917,60;77;19,80;177;8,60;2445;1,50;3948;2,50;12741;3,70;21258;10,50;54564;2,30;426817;2,60;JQ 308 4438;; +25072;MARDI;09/09/2025;1;09/12/2025;3;24;13;39;40;8;2;-3-13-24-39-40-;-2-8-;0;0;0;0;0;0;1;5;123026,70;3;21;1728,40;116;598;111,80;337;1542;45,80;236;1321;37,50;4785;22193;11,10;5216;24829;11,10;11724;56664;9,10;26035;116099;5,30;79931;360618;5,40;177273;807700;3,90;0;0;1;2123,60;55;21,40;169;6,90;2352;1,20;2623;3,00;12648;2,90;22211;7,70;39958;2,40;349664;2,40;HW 329 7662;; +25071;VENDREDI;05/09/2025;5;05/12/2025;30;27;41;31;43;5;8;-27-30-31-41-43-;-5-8-;0;1;65278573,00;0;1;743932,40;0;5;34773,80;6;30;1805,20;146;621;160,60;386;1934;54,50;283;1386;53,40;6433;30838;12,00;6964;32080;12,80;14020;67211;11,40;38510;179491;5,10;104276;493537;5,90;225078;1046162;4,50;0;0;4;768,20;67;25,40;191;8,90;3045;1,40;3419;3,30;18018;2,90;32593;7,60;50980;2,70;527451;2,30;EQ 858 3674;; +25070;MARDI;02/09/2025;4;02/12/2025;31;32;36;13;30;12;1;-13-30-31-32-36-;-1-12-;0;0;0;0;2;284860,20;0;4;33288,20;1;20;2073,60;80;429;178,00;205;982;82,20;185;1064;53,30;3284;15911;17,80;4187;20546;15,40;11003;54062;10,90;19157;86825;8,20;68157;323676;6,90;187995;880244;4,10;0;0;1;2346,20;44;29,60;112;11,60;1734;1,80;2162;4,00;10193;4,00;16837;11,30;35138;3,00;339671;2,70;GX 069 8683;; +25069;VENDREDI;29/08/2025;3;28/11/2025;6;13;37;10;9;1;7;-6-9-10-13-37-;-1-7-;0;0;0;1;5;138979,10;0;2;81204,30;22;65;778,20;238;910;102,40;667;2147;45,80;512;1893;36,50;9002;32086;10,70;10961;41690;9,20;21806;87909;8,10;39308;150987;5,70;142083;574759;4,70;282781;1208430;3,60;1;1580,40;6;210,70;108;14,60;294;5,30;4124;,90;5205;2,00;18396;2,70;25382;9,00;67540;1,90;448752;2,50;IA 474 1654;; +25068;MARDI;26/08/2025;2;25/11/2025;2;42;38;14;45;11;10;-2-14-38-42-45-;-10-11-;0;0;0;0;2;255796,20;1;4;29891,90;4;19;1960,10;93;468;146,50;175;832;87,10;235;1079;47,20;2483;12514;20,30;4320;20246;14,00;11142;50826;10,40;13365;68046;9,40;63919;295080;6,80;171850;770372;4,20;0;0;4;539,40;58;20,60;84;14,20;1326;2,20;2276;3,50;7049;5,30;12155;14,30;33101;2,90;317911;2,70;QW 618 1429;; +25067;VENDREDI;22/08/2025;1;21/11/2025;39;30;9;12;6;10;4;-6-9-12-30-39-;-4-10-;0;0;0;1;2;330801,20;1;11;14057,00;5;36;1337,80;179;823;107,80;389;1602;58,50;519;2100;31,30;5091;21707;15,10;8213;34752;10,50;22643;91081;7,50;24634;109421;7,50;108318;465401;5,60;277193;1170392;3,50;0;0;3;903,30;66;22,80;196;7,60;2507;1,50;4033;2,50;12565;3,70;20171;10,80;53481;2,30;399661;2,70;BO 496 3223;; +25066;MARDI;19/08/2025;18;18/11/2025;24;43;41;34;31;8;6;-24-31-34-41-43-;-6-8-;1;1;250000000,00;4;7;270238,90;2;7;38515,60;26;78;1076,60;313;1150;134,50;622;2489;65,70;583;2286;50,20;9855;38081;15,00;11635;47427;13,50;25966;105829;11,20;56596;214362;6,70;183800;734877;6,10;407128;1622924;4,50;0;0;3;1717,10;144;19,80;291;9,80;4523;1,50;5332;3,50;25750;3,50;47903;8,70;84274;2,80;896145;2,30;VK 747 8211;; +25065;VENDREDI;15/08/2025;17;14/11/2025;36;40;35;30;13;6;2;-13-30-35-36-40-;-2-6-;0;0;0;0;2;592222,90;2;13;21294,10;16;63;1368,60;203;1023;155,20;540;2481;67,60;485;2339;50,40;8978;40120;14,70;10232;48421;13,50;23213;109005;11,20;55801;234111;6,30;168516;759032;6,10;366018;1688844;4,40;0;0;9;525,90;96;27,30;246;10,60;3968;1,60;4796;3,60;25162;3,20;44960;8,50;77998;2,70;824436;2,30;JZ 186 6062;; +25064;MARDI;12/08/2025;16;11/11/2025;46;48;28;18;42;3;9;-18-28-42-46-48-;-3-9-;0;0;0;0;1;970058,30;1;6;37786,40;15;58;1217,50;190;839;155,00;557;2359;58,20;418;1914;50,40;9028;36957;13,00;9876;42492;12,60;21681;92517;10,80;54129;215719;5,60;152183;640893;5,90;325985;1364969;4,50;0;0;4;1024,10;92;24,70;251;9,00;4159;1,30;4624;3,20;24565;2,90;42990;7,70;70473;2,60;715143;2,30;IT 232 9034;; +25063;VENDREDI;08/08/2025;15;07/11/2025;2;34;19;12;44;10;6;-2-12-19-34-44-;-6-10-;0;0;0;2;6;183180,90;8;21;12232,10;26;72;1111,20;510;1464;100,60;576;2636;59,10;1629;4276;25,60;8400;36400;15,00;13746;54367;11,20;35836;136121;8,30;41930;185239;7,40;185421;761556;5,60;448412;1883537;3,70;1;2472,40;11;179,80;231;10,70;272;9,00;3992;1,50;6491;2,50;20175;3,80;32203;11,20;87041;2,30;685511;2,60;FU 501 2143;; +25062;MARDI;05/08/2025;14;04/11/2025;1;3;5;42;47;5;10;-1-3-5-42-47-;-5-10-;0;0;0;0;3;281225,80;1;5;39436,20;5;28;2193,40;132;736;153,70;419;1848;64,70;363;1683;49,90;6465;27758;15,10;8786;38712;12,10;20396;89293;9,70;35589;154890;6,80;135205;579368;5,70;307433;1303797;4,10;0;0;3;1153,20;55;34,90;182;10,50;3067;1,50;4264;3,00;16987;3,50;26539;10,50;64143;2,40;544786;2,50;DL 585 0537;; +25061;VENDREDI;01/08/2025;13;31/10/2025;29;4;25;16;30;10;2;-4-16-25-29-30-;-2-10-;0;0;0;0;3;331615,70;4;14;16608,00;17;68;1065,00;269;1313;101,60;546;2461;57,30;786;3201;30,90;7385;34085;14,50;11864;52194;10,50;28725;126467;8,10;38234;172275;7,20;156572;704224;5,50;375741;1683554;3,70;0;0;9;432,70;137;15,70;280;7,70;3562;1,50;5554;2,60;18443;3,60;30796;10,20;73809;2,40;606018;2,50;AX 511 9972;; +25060;MARDI;29/07/2025;12;28/10/2025;5;46;44;6;42;4;8;-5-6-42-44-46-;-4-8-;0;0;0;0;4;193669,00;1;5;36210,90;6;25;2255,70;114;582;178,40;285;1651;66,50;273;1380;55,90;5594;28236;13,60;5655;30308;14,20;12523;66114;12,10;37906;174670;5,50;99074;500434;6,10;224250;1121436;4,30;0;0;5;553,50;50;30,70;125;12,30;2543;1,50;2760;3,70;16737;2,80;25529;8,70;47523;2,60;449065;2,40;RE 350 7487;; +25059;VENDREDI;25/07/2025;11;24/10/2025;32;36;23;6;7;11;12;-6-7-23-32-36-;-11-12-;0;0;0;1;5;180631,10;2;9;23453,50;6;27;2435,00;153;854;141,80;291;1611;79,40;393;2261;39,70;4232;23762;18,90;7132;39057;12,80;21840;109645;8,50;23064;126862;8,90;107613;568250;6,20;325664;1597566;3,50;0;0;5;663,00;74;24,80;147;12,50;2213;2,00;3622;3,40;11847;4,80;19421;13,80;54056;2,80;458727;2,90;LF 343 4676;; +25058;MARDI;22/07/2025;10;21/10/2025;8;41;33;26;15;9;10;-8-15-26-33-41-;-9-10-;0;0;0;1;5;144005,00;0;13;12944,70;3;20;2620,70;190;955;101,10;264;1355;75,30;416;2090;34,30;3499;19255;18,60;6904;35872;11,10;16129;82421;9,00;19504;102270;8,80;97854;500245;5,60;234721;1172483;3,90;1;1461,20;2;584,50;102;14,30;122;11,90;1722;2,10;3145;3,10;9797;4,60;16140;13,20;46625;2,50;419618;2,50;VM 159 6614;; +25057;VENDREDI;18/07/2025;9;17/10/2025;25;19;42;45;13;9;2;-13-19-25-42-45-;-2-9-;0;0;0;1;8;114872,30;1;8;26847,50;12;52;1286,50;266;1189;103,60;541;2628;49,50;479;2432;37,60;7865;37716;12,10;10504;49680;10,20;22348;106931;8,80;39413;190854;6,00;150549;709562;5,10;316282;1500884;3,80;0;0;5;676,30;117;16,00;238;7,80;3619;1,20;4843;2,50;18497;3,10;28589;9,50;70688;2,10;547619;2,40;TR 056 2866;; +25056;MARDI;15/07/2025;8;14/10/2025;41;45;24;49;38;1;6;-24-38-41-45-49-;-1-6-;0;0;0;0;1;625081,30;3;10;14609,10;3;24;1896,00;94;503;166,60;212;1155;76,70;246;1242;50,10;3360;17486;17,80;4713;24263;14,30;11068;58905;10,90;19528;99931;7,80;69788;362410;6,80;162882;859234;4,60;0;0;1;2297,60;47;27,10;100;12,70;1663;1,90;2293;3,70;9526;4,20;18936;9,80;34606;3,00;395050;2,30;BY 654 8780;; +25055;VENDREDI;11/07/2025;7;10/10/2025;8;24;45;23;49;2;10;-8-23-24-45-49-;-2-10-;0;0;0;0;1;762238,80;1;8;22268,40;4;32;1734,00;124;724;141,10;319;1520;71,00;353;1754;43,20;5273;24375;15,50;6843;33587;12,60;16877;81649;9,60;27884;130005;7,30;108533;510338;5,80;260627;1233108;3,90;0;0;2;1487,70;67;24,60;167;9,80;2715;1,50;3439;3,20;13863;3,70;23011;10,40;53524;2,50;459152;2,60;NG 234 1973;; +25054;MARDI;08/07/2025;6;07/10/2025;50;18;9;1;8;5;1;-1-8-9-18-50-;-1-5-;0;0;0;1;9;66695,40;0;6;23381,70;8;44;993,10;226;974;82,60;398;1898;44,80;366;1736;34,40;5235;23391;12,70;8115;37143;8,90;16254;72418;8,50;24212;112534;6,60;102932;470066;5,00;217983;985419;3,80;0;0;6;388,90;105;12,30;201;6,40;2516;1,20;3873;2,20;11636;3,50;16789;11,20;49661;2,10;360609;2,60;PR 568 2088;; +25053;VENDREDI;04/07/2025;5;03/10/2025;45;19;48;29;42;5;7;-19-29-42-45-48-;-5-7-;0;0;0;0;1;723367,00;4;10;16906,20;5;39;1350,20;137;661;146,70;440;2016;50,80;270;1392;51,70;7536;32284;11,10;6978;32558;12,30;13815;66828;11,10;43109;184953;4,90;110770;506291;5,60;218418;1025052;4,40;0;0;1;2878,10;73;21,90;199;8,00;3516;1,10;3395;3,10;20129;2,50;35093;6,60;53291;2,40;532587;2,10;ND 836 8918;; +25052;MARDI;01/07/2025;4;30/09/2025;34;17;1;28;32;7;8;-1-17-28-32-34-;-7-8-;0;0;0;0;3;186076,90;5;8;16308,40;3;34;1195,20;128;602;124,30;227;1231;64,20;212;1122;49,50;3841;18291;15,20;6491;29582;10,40;11454;55354;10,40;21258;97882;7,10;95831;433847;5,00;171061;825734;4,20;0;0;1;2230,00;57;21,70;100;12,30;1860;1,60;3125;2,60;10338;3,70;17289;10,40;46300;2,20;410470;2,10;JC 350 5018;; +25051;VENDREDI;27/06/2025;3;26/09/2025;27;45;19;36;49;7;10;-19-27-36-45-49-;-7-10-;0;0;0;0;5;139732,90;2;6;27214,90;5;34;1495,90;166;790;118,50;363;1525;64,90;330;1643;42,30;5013;21480;16,20;6971;32660;11,80;14875;72964;9,90;28035;119182;7,30;102363;467164;5,90;216491;1027285;4,30;0;0;1;2812,50;73;21,40;195;8,00;2512;1,50;3458;3,00;14083;3,40;24462;9,30;50584;2,50;486275;2,30;EL 697 7800;; +25050;MARDI;24/06/2025;2;23/09/2025;16;21;39;7;23;11;7;-7-16-21-23-39-;-7-11-;0;0;0;1;4;129694,40;1;2;60623,40;4;31;1218,20;149;718;96,80;315;1708;43,00;338;1602;32,20;4417;22541;11,40;7158;33379;8,60;14871;68902;7,70;21377;102456;6,30;98913;442550;4,60;207372;942225;3,40;0;0;1;2144,60;69;17,20;163;7,30;2203;1,30;3514;2,20;11026;3,40;15818;10,90;48406;2,00;337100;2,50;KD 688 9409;; +25049;VENDREDI;20/06/2025;1;19/09/2025;37;24;47;5;8;9;3;-5-8-24-37-47-;-3-9-;0;0;0;1;4;172608,90;1;4;40341,50;10;41;1225,80;173;814;113,70;506;2032;48,10;357;1596;43,00;7201;30280;11,30;8048;34959;10,90;17178;72626;9,80;38484;160260;5,30;118866;510014;5,30;250880;1064811;4,10;1;1673,40;3;446,20;86;19,40;249;6,70;3497;1,10;4041;2,70;18753;2,80;32578;7,40;58690;2,30;518453;2,30;WN 956 0331;; +25048;MARDI;17/06/2025;21;16/09/2025;22;44;49;23;13;3;5;-13-22-23-44-49-;-3-5-;0;1;250000000,00;0;3;7825661,00;5;16;20064,10;18;77;1298,50;369;1609;114,40;1028;4042;48,10;739;3302;41,40;14300;59720;11,40;18590;76119;10,00;37663;153398;9,20;73453;308585;5,50;276137;1129769;4,70;543331;2222499;3,90;0;0;10;572,80;179;17,70;476;6,60;6464;1,20;8516;2,50;33269;3,00;52235;8,80;125738;2,00;959702;2,40;PG 494 5686;; +25047;VENDREDI;13/06/2025;20;12/09/2025;43;40;45;2;28;3;7;-2-28-40-43-45-;-3-7-;0;0;0;0;7;4493785,70;5;17;25302,20;25;86;1557,80;373;1613;153,00;1112;4505;57,90;852;3546;51,70;19638;73437;12,40;19135;76430;13,30;39417;163963;11,60;122516;450381;5,10;304082;1191575;6,00;623914;2534452;4,60;0;0;14;603,30;188;24,90;484;9,60;9125;1,20;9310;3,30;55163;2,60;107972;6,30;145353;2,60;1570697;2,10;OW 405 0185;; +25046;MARDI;10/06/2025;19;09/09/2025;36;40;45;39;19;5;6;-19-36-39-40-45-;-5-6-;0;0;0;1;4;5914294,60;5;12;26957,40;13;56;1799,20;255;1221;152,00;588;2672;73,40;548;2723;50,60;8658;41884;16,40;13031;58295;13,10;28063;127177;11,20;50281;235400;7,30;203475;900370;6,00;429572;1918167;4,50;1;3157,50;4;631,50;114;27,60;277;11,30;4129;1,90;6091;3,40;23359;4,20;43394;10,60;94230;2,70;1032154;2,20;UF 448 1097;; +25045;VENDREDI;06/06/2025;18;05/09/2025;30;29;21;20;35;2;12;-20-21-29-30-35-;-2-12-;0;0;0;1;7;2737810,60;3;18;23054,20;32;99;1305,60;451;1764;134,90;978;3682;68,30;956;3843;46,00;13560;53529;16,50;20837;82454;11,90;48241;186493;9,80;72845;283271;7,80;301684;1198212;5,80;731493;2871563;3,90;1;4476,70;17;210,60;222;20,10;427;10,40;6497;1,70;9886;3,00;35454;3,90;57627;11,30;143162;2,50;1257341;2,50;KR 483 4676;; +25044;MARDI;03/06/2025;17;02/09/2025;47;12;15;38;48;7;5;-12-15-38-47-48-;-5-7-;0;0;0;1;6;212330,10;3;9;33083,30;10;77;1204,40;257;1180;144,70;862;3348;53,90;569;2562;49,50;13507;52778;12,00;13732;57834;12,20;28362;120208;10,90;76933;294124;5,40;216553;881610;5,70;441467;1826079;4,40;1;3041,70;2;1216,60;143;21,20;399;7,60;6090;1,20;6496;3,10;34336;2,70;58767;7,50;100125;2,50;970006;2,20;DK 401 0261;; +25043;VENDREDI;30/05/2025;16;29/08/2025;36;7;14;4;33;1;5;-4-7-14-33-36-;-1-5-;0;0;0;6;10;126179,90;7;18;16383,50;19;59;1556,80;280;1313;128,80;693;3138;57,00;737;3336;37,60;10383;44407;14,10;15125;66133;10,50;37103;157111;8,30;53120;226299;6,90;214912;932814;5,30;498772;2129110;3,70;0;0;7;727,50;126;22,40;314;9,00;4770;1,40;6864;2,70;23871;3,70;38744;10,60;98241;2,30;804227;2,50;BB 232 0733;; +25042;MARDI;27/05/2025;15;26/08/2025;30;41;38;12;40;4;12;-12-30-38-40-41-;-4-12-;0;0;0;0;2;486735,00;2;10;22751,60;12;42;1687,20;220;891;146,50;397;1815;76,00;442;1987;48,80;6570;27160;17,80;9775;40249;13,40;22920;94907;10,60;37210;147631;8,20;150047;601177;6,30;364581;1451448;4,20;0;0;6;719,50;98;24,40;179;13,30;3100;1,90;4622;3,40;17798;4,20;30184;11,60;70156;2,80;671279;2,50;QR 074 0084;; +25041;VENDREDI;23/05/2025;14;22/08/2025;49;43;29;46;10;12;7;-10-29-43-46-49-;-7-12-;0;0;0;1;4;270867,00;3;10;25322,40;5;43;1834,20;191;882;164,70;401;1925;79,70;441;2151;50,10;6646;29802;18,10;9447;43716;13,70;20899;97583;11,40;36828;167141;8,10;149202;673673;6,30;332536;1527587;4,50;0;0;3;1409,60;101;23,20;193;12,10;3346;1,70;4642;3,30;18133;4,00;31963;10,70;70970;2,70;723896;2,30;RB 444 2267;; +25040;MARDI;20/05/2025;13;19/08/2025;47;1;29;8;13;5;6;-1-8-13-29-47-;-5-6-;0;0;0;0;1;839110,40;1;7;28016,20;13;49;1246,60;262;1034;108,80;456;2060;57,70;526;2154;38,80;6297;28703;14,50;10801;45087;10,30;23214;96677;8,90;31974;148864;7,00;153230;644453;5,10;316727;1352916;3,90;0;0;5;663,90;108;17,00;218;8,40;2868;1,60;4913;2,50;14841;3,90;23375;11,50;70321;2,10;555450;2,40;AW 582 7504;; +25039;VENDREDI;16/05/2025;12;15/08/2025;25;9;37;46;6;6;12;-6-9-25-37-46-;-6-12-;0;0;0;1;5;199795,20;1;7;33353,80;13;50;1454,40;224;1074;124,70;515;2132;66,40;498;2348;42,30;7105;30789;16,10;10181;44698;12,40;24356;106981;9,60;38153;160167;7,80;152661;656758;6,00;367525;1578227;4,00;1;2260,50;6;301,40;112;20,10;258;8,70;3576;1,50;4949;3,00;18948;3,70;30757;10,70;72852;2,50;618414;2,60;CI 695 9575;; +25038;MARDI;13/05/2025;11;12/08/2025;19;9;44;47;50;2;9;-9-19-44-47-50-;-2-9-;0;0;0;1;2;382845,10;0;4;44738,60;7;48;1161,20;149;760;135,10;450;2167;50,00;293;1643;46,40;6504;29645;12,80;9218;46202;9,20;16322;81295;9,70;33889;156013;6,10;121166;558850;5,40;244061;1142108;4,20;0;0;3;986,00;70;23,40;208;7,90;3020;1,30;4321;2,50;15809;3,20;25509;9,40;57291;2,30;497520;2,40;SQ 356 6673;; +25037;VENDREDI;09/05/2025;10;08/08/2025;15;18;25;47;29;9;5;-15-18-25-29-47-;-5-9-;0;0;0;2;6;153430,40;2;6;35859,20;14;69;971,20;195;1070;115,30;637;3254;40,10;445;2190;41,80;8672;43669;10,50;10374;49730;10,20;21264;100159;9,50;44882;219063;5,20;148315;704694;5,10;306617;1430207;4,00;0;0;9;389,60;98;19,80;273;7,10;3876;1,20;4726;2,70;20137;3,00;32223;8,80;68572;2,30;580251;2,40;EI 835 2462;; +25036;MARDI;06/05/2025;9;05/08/2025;23;24;47;48;8;4;9;-8-23-24-47-48-;-4-9-;0;0;0;1;3;226674,40;1;7;22704,60;9;34;1455,90;139;590;154,50;377;1803;53,40;292;1386;48,80;6322;28867;11,70;6957;30711;12,30;14989;68538;10,20;34842;156396;5,40;109081;476415;5,60;240583;1050239;4,10;1;1545,90;3;412,20;57;27,10;176;8,70;2870;1,30;3305;3,10;15707;3,10;26008;8,60;50621;2,50;450313;2,40;LW 054 7366;; +25035;VENDREDI;02/05/2025;8;01/08/2025;21;49;5;3;19;1;10;-3-5-19-21-49-;-1-10-;0;0;0;1;4;209233,30;3;8;24450,60;8;42;1450,60;242;1050;106,80;376;1847;64,20;647;2832;29,40;5126;25353;16,40;9098;43044;10,80;25042;116371;7,40;24254;127471;8,20;121450;591292;5,50;328304;1576485;3,30;0;0;3;967,20;124;13,00;193;8,30;2562;1,50;4334;2,40;12325;4,10;18711;12,50;57625;2,30;412808;2,80;SN 962 2277;; +25034;MARDI;29/04/2025;7;29/07/2025;24;30;41;2;9;11;1;-2-9-24-30-41-;-1-11-;0;0;0;0;0;0;2;11;65420,20;7;32;1326,90;130;605;129,20;229;1156;71,50;264;1502;38,60;3301;16848;17,20;5267;25854;12,50;14499;67339;8,90;17239;89036;8,20;76684;373985;6,10;209656;972679;3,80;0;0;4;551,60;74;16,50;125;9,80;1664;1,80;2476;3,30;8614;4,40;14325;12,40;37303;2,70;321126;2,70;DN 431 1820;; +25033;VENDREDI;25/04/2025;6;25/07/2025;13;32;39;41;22;1;12;-13-22-32-39-41-;-1-12-;0;0;0;0;5;148333,50;1;5;34667,90;11;41;1316,80;140;696;142,80;283;1345;78,10;446;2202;33,50;3890;20366;18,10;5819;29462;13,90;17005;85992;8,90;21838;111027;8,30;82342;426810;6,80;239976;1213128;3,80;0;0;7;396,80;66;23,30;142;10,80;2033;1,80;2954;3,50;11463;4,20;20291;11,10;41059;3,10;403059;2,70;UF 496 9318;; +25032;MARDI;22/04/2025;5;22/07/2025;37;47;33;7;28;9;12;-7-28-33-37-47-;-9-12-;0;0;0;0;0;0;0;4;161263,00;7;28;1359,30;88;498;140,70;197;947;78,20;241;1250;41,60;2724;13963;18,60;4633;24882;11,60;12233;62197;8,60;14302;74011;8,80;68503;342032;6,00;175513;848025;3,90;0;0;2;988,70;37;29,60;120;9,10;1365;2,00;2284;3,20;7415;4,60;12569;12,70;33612;2,60;303179;2,60;VQ 339 8773;; +25031;VENDREDI;18/04/2025;4;18/07/2025;20;48;39;35;27;3;8;-20-27-35-39-48-;-3-8-;0;0;0;1;5;134387,60;1;6;26173,80;9;44;1111,70;150;694;129,80;329;1760;54,10;310;1397;47,90;5549;26824;12,40;6425;30751;12,10;13266;63633;10,90;31846;148487;5,60;97192;452131;5,80;198671;941740;4,50;0;0;7;392,60;73;20,90;152;10,00;2674;1,40;3215;3,10;15219;3,10;27914;7,90;47799;2,60;491736;2,20;RF 024 0852;; +25030;MARDI;15/04/2025;3;15/07/2025;3;31;38;44;33;12;6;-3-31-33-38-44-;-6-12-;0;0;0;0;0;0;0;5;127814,00;6;23;1639,50;57;415;167,30;192;825;89,00;197;1042;49,50;3158;14405;17,90;4223;20484;14,00;11129;52563;10,10;17443;77515;8,30;65802;314277;6,50;168309;807850;4,00;0;0;4;521,70;30;38,60;109;10,60;1639;1,70;2151;3,60;9213;3,90;15796;10,70;33379;2,80;323426;2,60;UE 731 6890;; +25029;VENDREDI;11/04/2025;2;11/07/2025;14;34;46;49;28;3;8;-14-28-34-46-49-;-3-8-;0;0;0;1;4;165122,30;0;6;25727,80;11;34;1414,10;147;621;142,60;349;1706;54,80;292;1336;49,20;5635;25904;12,60;6066;28376;12,90;13021;59927;11,40;32298;145952;5,60;98232;438492;5,90;203478;914696;4,50;1;1567,90;10;125,40;79;19,80;174;9,00;2694;1,40;3078;3,40;15170;3,20;28657;7,90;48326;2,60;505328;2,20;HN 251 6760;; +25028;MARDI;08/04/2025;1;08/07/2025;15;3;48;14;49;1;7;-3-14-15-48-49-;-1-7-;0;0;0;2;4;121704,90;1;4;28444,40;4;26;1363,00;99;468;139,40;275;1091;63,20;175;858;56,50;4439;17230;14,00;4459;21045;12,80;9326;45047;11,10;24329;96020;6,30;76015;336626;5,70;156557;716092;4,30;2;573,00;1;916,90;50;22,90;121;9,40;2174;1,30;2249;3,40;11722;3,00;20045;8,30;37960;2,40;352651;2,30;DX 524 3301;; +25027;VENDREDI;04/04/2025;2;04/07/2025;50;27;39;45;41;7;5;-27-39-41-45-50-;-5-7-;1;1;30117535,00;1;1;684735,20;0;5;32006,70;2;28;1780,20;129;597;153,80;366;1751;55,40;259;1386;49,20;6027;27314;12,40;6342;29436;12,90;12645;60328;11,70;38546;165425;5,10;95497;444405;6,00;190705;886742;4,90;1;1677,90;1;1342,30;62;27,00;170;9,80;2833;1,40;3208;3,50;17866;2,90;37860;6,40;47519;2,90;583594;2,00;CQ 159 4215;; +25026;MARDI;01/04/2025;1;01/07/2025;30;49;46;9;24;4;12;-9-24-30-46-49-;-4-12-;0;0;0;0;2;266728,50;3;9;13853,00;2;9;4314,80;96;393;182,00;218;921;82,10;226;998;53,20;3257;14128;18,80;5218;21031;14,00;12430;51822;10,60;18342;77262;8,60;78746;320432;6,50;199795;810737;4,10;0;0;1;2452,10;51;26,70;124;10,90;1669;2,00;2612;3,40;9489;4,50;16264;12,20;39983;2,80;369561;2,60;OH 642 3223;; +25025;VENDREDI;28/03/2025;8;27/06/2025;10;21;30;45;42;1;9;-10-21-30-42-45-;-1-9-;0;1;250000000,00;5;9;338645,80;9;24;18585,60;36;116;1197,70;675;2040;125,40;1562;4599;58,80;1513;4772;39,80;20673;64252;14,70;28578;88580;11,90;69243;210303;9,30;108134;338662;7,00;420996;1300647;5,70;1051344;3111406;3,80;3;1990,90;10;477,80;308;19,30;692;8,60;9473;1,50;13202;3,00;49866;3,70;83519;10,40;195689;2,50;1696563;2,50;FF 641 9577;; +25024;MARDI;25/03/2025;7;24/06/2025;36;3;18;33;1;6;2;-1-3-18-33-36-;-2-6-;0;0;0;0;2;616553,40;1;8;36024,60;14;69;1300,90;372;1334;123,90;881;3076;56,80;703;2774;44,20;13205;46584;13,10;16027;59429;11,50;34833;132501;9,60;69546;247081;6,20;240339;876893;5,50;521721;1942643;4,00;0;0;6;971,40;183;17,60;407;7,90;5919;1,30;7431;2,90;30788;3,30;52442;9,00;110488;2,40;985309;2,30;DQ 142 2803;; +25023;VENDREDI;21/03/2025;6;20/06/2025;9;17;18;21;50;12;5;-9-17-18-21-50-;-5-12-;0;0;0;1;4;330212,80;5;17;18159,10;18;72;1335,40;392;1407;125,80;803;2983;62,70;913;3328;39,50;11191;41905;15,60;18582;69007;10,60;45023;161655;8,40;57935;214175;7,70;264214;977456;5,30;649044;2319001;3,60;0;0;8;790,90;196;17,90;382;9,20;5340;1,60;8524;2,70;27933;3,90;43278;11,80;122096;2,30;944454;2,70;LI 392 0967;; +25022;MARDI;18/03/2025;5;17/06/2025;46;9;35;4;50;8;11;-4-9-35-46-50-;-8-11-;0;0;0;0;3;332090,30;0;6;38807,40;9;33;2197,70;205;848;157,50;443;1879;75,10;389;1793;55,30;6844;28656;17,30;9377;39406;14,00;21968;91297;11,20;39447;160779;7,70;152825;620751;6,30;370686;1447963;4,30;0;0;4;1138,10;102;24,70;217;11,60;3343;1,80;4459;3,80;18955;4,20;32673;11,30;72159;2,80;736962;2,40;ET 758 3936;; +25021;VENDREDI;14/03/2025;4;13/06/2025;8;33;49;35;10;2;9;-8-10-33-35-49-;-2-9-;0;0;0;2;3;387524,60;2;8;33964,00;11;55;1538,70;257;1126;138,40;645;2642;62,30;621;2540;45,50;10559;41736;13,80;12934;52637;12,20;28353;114586;10,40;59201;232010;6,20;203369;806481;5,60;447185;1755386;4,20;0;0;8;653,90;133;21,80;283;10,20;4724;1,50;6113;3,10;27173;3,30;45994;9,20;94187;2,50;878881;2,40;RO 531 0083;; +25020;MARDI;11/03/2025;3;10/06/2025;36;37;42;47;13;11;7;-13-36-37-42-47-;-7-11-;0;0;0;1;3;302199,80;4;10;21188,70;5;37;1783,70;191;800;151,90;496;2056;62,50;376;1679;53,70;7165;30755;14,60;9028;39147;12,80;19083;82665;11,30;39888;167267;6,70;142894;596277;6,00;301940;1269940;4,50;0;0;3;1303,00;95;22,80;219;9,90;3239;1,60;4077;3,50;18596;3,60;32282;9,80;66164;2,70;675483;2,30;HT 340 0230;; +25019;VENDREDI;07/03/2025;2;06/06/2025;32;6;48;27;10;8;3;-6-10-27-32-48-;-3-8-;0;0;0;2;3;429799,40;3;12;25112,80;26;89;1054,60;418;1507;114,70;983;3784;48,30;874;3081;41,60;14093;53706;11,90;17544;64892;11,00;36382;136202;9,70;74527;286792;5,60;258524;946610;5,30;539716;1986190;4,10;1;3437,90;7;392,90;195;17,60;397;8,60;6320;1,30;8137;2,80;34077;3,10;57595;8,70;120236;2,30;1047190;2,30;NC 896 8360;; +25018;MARDI;04/03/2025;1;03/06/2025;29;42;15;20;35;9;2;-15-20-29-35-42-;-2-9-;0;0;0;2;7;68564,80;1;5;22434,60;6;28;1247,80;106;530;121,40;267;1221;55,70;223;1141;41,90;3909;18059;13,20;4771;22979;11,60;10188;48863;10,10;20874;96708;6,20;73372;344094;5,50;153613;724716;4,20;1;1060,20;3;282,70;48;22,00;136;7,70;1877;1,40;2383;2,90;9962;3,30;16646;9,20;35751;2,40;326024;2,30;HB 180 2007;; +25017;VENDREDI;28/02/2025;4;30/05/2025;43;31;46;16;7;9;5;-7-16-31-43-46-;-5-9-;0;1;52448674,00;0;2;353112,60;0;8;20632,00;4;35;1468,80;162;702;134,90;372;1975;50,60;283;1485;47,30;5656;29005;12,10;6731;33630;11,60;14114;69321;10,50;31772;161746;5,40;102444;490638;5,60;215501;1023433;4,30;0;0;0;0;80;52,00;165;9,00;2565;1,40;3219;3,00;14543;3,20;25403;8,50;48593;2,50;453565;2,30;RD 782 8358;; +25016;MARDI;25/02/2025;3;27/05/2025;23;24;44;17;22;11;6;-17-22-23-24-44-;-6-11-;0;0;0;1;3;167896,00;1;6;19620,00;5;22;1666,60;126;544;124,10;243;1169;61,00;260;1374;36,50;3660;17726;14,10;5632;27761;10,00;13903;69403;7,50;18035;88397;7,10;76685;373133;5,30;193603;929020;3,40;1;1034,70;2;413,80;68;15,20;123;8,40;1785;1,40;2636;2,60;9025;3,60;13102;11,50;36884;2,30;273443;2,70;GT 442 6106;; +25015;VENDREDI;21/02/2025;2;23/05/2025;1;43;3;20;11;11;2;-1-3-11-20-43-;-2-11-;0;0;0;0;3;213502,40;4;10;14969,70;9;32;1457,00;159;745;115,20;367;1584;57,30;391;1752;36,40;4683;21878;14,50;7647;33261;10,60;18450;77813;8,50;23967;113178;7,00;101233;461955;5,40;247414;1088811;3,70;0;0;6;409,70;66;20,60;173;7,80;2257;1,50;3546;2,50;11818;3,60;18456;10,80;48572;2,30;369476;2,60;AC 559 3492;; +25014;MARDI;18/02/2025;1;20/05/2025;26;40;5;25;14;5;7;-5-14-25-26-40-;-5-7-;0;0;0;1;1;469895,30;0;4;27455,50;7;38;900,10;118;596;105,70;432;2036;32,70;224;1171;39,90;6243;28069;8,30;5826;27683;9,40;10513;50884;9,50;29894;133047;4,40;82964;392145;4,70;148346;720876;4,10;0;0;1;1801,00;63;15,80;211;4,70;2862;,80;2716;2,40;13338;2,30;19866;7,30;39096;2,10;314532;2,30;IW 657 0898;; +25013;VENDREDI;14/02/2025;6;16/05/2025;38;14;36;31;4;10;3;-4-14-31-36-38-;-3-10-;0;1;78450739,00;1;2;442087,50;0;5;41329,20;12;45;1430,30;130;770;153,90;322;1735;72,20;278;1803;48,80;5428;28237;15,50;6444;36861;13,30;15140;84809;10,70;31325;158678;6,90;105904;572136;6,00;247653;1322764;4,20;1;1720,00;6;229,30;70;24,50;140;12,20;2619;1,60;3241;3,50;15265;3,50;26628;9,40;51943;2,70;512859;2,40;QN 143 4928;; +25012;MARDI;11/02/2025;5;13/05/2025;21;3;46;36;41;6;1;-3-21-36-41-46-;-1-6-;0;0;0;1;5;113658,30;2;4;33204,70;4;30;1378,90;100;519;146,80;195;1056;76,20;198;1087;52,00;3222;16549;17,10;4129;21756;14,50;9343;51207;11,40;19600;94596;7,50;67784;343915;6,50;153996;805259;4,40;1;1176,30;2;470,50;53;22,10;97;12,10;1497;1,90;2022;3,80;9257;4,00;17228;9,90;32730;2,90;356426;2,30;MT 990 8988;; +25011;VENDREDI;07/02/2025;4;09/05/2025;10;24;45;29;23;8;10;-10-23-24-29-45-;-8-10-;0;0;0;0;0;0;0;6;146483,20;8;48;1080,40;150;673;141,90;342;1634;61,80;342;1570;45,20;5109;22992;15,40;7629;35090;11,20;17560;81279;9,00;25489;115925;7,60;110924;508548;5,50;264731;1199016;3,70;0;0;2;1374,00;63;24,20;162;9,40;2554;1,40;3673;2,70;12872;3,70;18662;11,90;53461;2,30;412347;2,60;BT 538 9173;; +25010;MARDI;04/02/2025;3;06/05/2025;29;43;3;39;4;5;10;-3-4-29-39-43-;-5-10-;0;0;0;0;0;0;0;1;643777,50;6;24;1582,70;89;463;151,10;208;1003;73,70;185;966;53,80;3250;16585;15,60;3983;21383;13,50;9987;49792;10,80;18217;91488;7,10;68671;341234;6,00;164677;792657;4,10;0;0;3;663,00;45;24,50;96;11,50;1618;1,70;2054;3,60;8834;3,90;15429;10,40;33345;2,70;316812;2,50;LY 186 2372;; +25009;VENDREDI;31/01/2025;2;02/05/2025;30;41;49;44;22;7;2;-22-30-41-44-49-;-2-7-;0;0;0;0;1;662510,10;1;3;51613,10;6;28;1722,40;114;529;167,90;364;1514;62,00;225;1165;56,60;6004;25940;12,70;5569;26701;13,70;11462;57187;11,90;38036;154092;5,30;92082;430353;6,00;182640;892502;4,70;0;0;3;864,40;57;25,20;160;9,00;2814;1,20;2725;3,50;17115;2,60;33001;6,30;44586;2,60;483980;2,10;CV 402 0872;; +25008;MARDI;28/01/2025;1;29/04/2025;42;41;37;49;9;9;1;-9-37-41-42-49-;-1-9-;0;0;0;0;1;474163,90;0;1;110819,90;5;20;1725,80;68;334;190,30;189;964;69,70;155;783;60,30;3026;14770;15,90;3400;17824;14,70;7796;40350;12,10;18403;85554;6,90;56553;282713;6,60;127010;631428;4,70;0;0;2;911,30;37;27,30;99;10,20;1421;1,70;1715;3,90;8725;3,60;15230;9,70;28203;2,90;300191;2,40;BE 304 1672;; +25007;VENDREDI;24/01/2025;8;25/04/2025;30;2;19;49;11;3;8;-2-11-19-30-49-;-3-8-;0;1;99397573,00;2;9;92865,40;5;14;13952,60;10;64;950,60;323;1193;93,90;586;2564;46,20;560;2385;34,90;7962;37569;11,00;10765;46224;10,00;22536;97848;8,80;40172;191091;5,40;144174;641405;5,10;296950;1351272;3,90;0;0;4;773,40;138;12,40;260;6,60;3425;1,20;4815;2,30;17756;3,00;28269;8,80;65568;2,10;516928;2,40;PK 962 2825;; +25006;MARDI;21/01/2025;7;22/04/2025;8;6;14;27;41;5;4;-6-8-14-27-41-;-4-5-;0;0;0;2;5;121051,80;1;6;23576,50;7;35;1258,80;204;837;96,90;346;1514;56,60;398;1748;34,40;4368;21547;13,90;7261;35112;9,50;15503;71448;8,70;22380;110536;6,80;99051;482098;4,90;204046;974545;3,90;1;1210,20;3;322,70;95;12,70;163;7,40;2058;1,40;3348;2,40;10475;3,60;16108;10,90;45532;2,10;356098;2,40;KV 091 2370;; +25005;VENDREDI;17/01/2025;6;18/04/2025;24;42;15;35;8;8;6;-8-15-24-35-42-;-6-8-;0;0;0;2;9;85022,70;0;13;13756,90;13;64;870,30;255;1052;97,50;519;2312;46,90;406;2032;37,50;7052;31713;12,00;8980;42987;9,80;17160;84911;9,30;34781;156737;6,10;121067;576078;5,20;242853;1192259;4,00;0;0;6;468,10;109;14,30;239;6,50;3236;1,20;4153;2,50;16145;3,00;24923;9,10;56230;2,20;461750;2,40;VA 724 9628;; +25004;MARDI;14/01/2025;5;15/04/2025;41;48;29;20;18;5;9;-18-20-29-41-48-;-5-9-;0;0;0;1;6;95604,50;0;5;26813,20;2;19;2197,80;92;491;156,60;280;1512;53,70;223;1095;52,10;4387;23134;12,30;5052;25394;12,50;10617;52451;11,30;25287;131241;5,40;78923;391066;5,70;167122;811530;4,40;1;1186,10;2;474,40;44;26,90;127;9,30;2062;1,40;2375;3,30;11520;3,20;19924;8,60;37567;2,60;364877;2,30;PK 626 8865;; +25003;VENDREDI;10/01/2025;4;11/04/2025;36;37;27;42;12;7;6;-12-27-36-37-42-;-6-7-;0;0;0;2;4;179667,00;0;3;55988,10;8;31;1687,60;173;801;120,30;376;1720;59,20;268;1557;45,90;5390;24794;14,40;8257;37007;10,70;13994;69838;10,60;28883;131395;6,80;122042;539324;5,20;215753;1040834;4,30;1;1531,40;1;1225,10;81;18,90;166;9,20;2501;1,50;3833;2,60;13556;3,50;22598;9,80;56673;2,20;507078;2,10;QJ 602 0619;; +25002;MARDI;07/01/2025;3;08/04/2025;41;20;35;33;47;4;12;-20-33-35-41-47-;-4-12-;0;0;0;0;1;521898,30;1;5;24395,20;3;14;2713,70;84;412;169,80;167;924;80,00;218;1026;50,60;2606;13140;19,70;3851;19699;14,70;9645;48369;11,10;14737;71968;9,00;59649;293098;7,00;149467;731851;4,50;0;0;2;1042,40;36;32,10;99;11,60;1399;2,00;1981;3,90;7701;4,70;14201;11,90;30016;3,10;334441;2,50;HE 598 4252;; +25001;VENDREDI;03/01/2025;2;04/04/2025;19;35;37;3;29;1;9;-3-19-29-35-37-;-1-9-;0;0;0;1;2;316853,40;1;6;24684,60;4;29;1590,70;146;608;139,70;388;1591;56,40;322;1418;44,50;5148;22935;13,70;6516;29727;11,80;15093;68729;9,50;27255;119525;6,60;96072;439059;5,60;224141;1008934;3,90;1;1394,60;2;557,80;77;18,10;183;7,60;2373;1,40;3112;3,00;12597;3,40;20128;10,10;46017;2,50;398155;2,50;SI 059 9896;; +24105;MARDI;31/12/2024;1;01/04/2025;33;24;19;26;28;12;8;-19-24-26-28-33-;-8-12-;0;0;0;1;4;188075,10;0;11;15984,00;3;32;1711,40;125;782;129,00;266;1606;66,40;332;1961;38,20;3761;22223;16,80;6214;36088;11,50;16236;90132;8,60;19095;118600;7,90;91289;525874;5,60;238214;1309459;3,60;1;1376,50;2;550,60;54;25,40;159;8,60;2009;1,70;3067;3,00;10221;4,20;16206;12,40;45874;2,40;366456;2,70;OI 368 4788;; +24104;VENDREDI;27/12/2024;3;28/03/2025;45;33;12;27;22;4;8;-12-22-27-33-45-;-4-8-;0;1;41205363,00;1;5;129991,80;1;9;16878,40;10;60;788,50;185;854;102,00;491;2174;42,30;439;1923;33,60;6732;29649;10,90;8229;36455;9,90;18600;80975;8,30;33536;147394;5,50;114649;496697;5,10;253883;1098086;3,70;0;0;7;372,50;81;17,80;228;6,30;2969;1,20;3859;2,50;15260;2,90;23390;9,00;53013;2,20;412119;2,50;KK 662 2144;; +24103;MARDI;24/12/2024;2;25/03/2025;6;24;16;23;10;12;11;-6-10-16-23-24-;-11-12-;0;0;0;1;2;306780,30;3;16;8962,40;7;32;1395,70;182;763;107,80;247;1285;67,60;707;2663;22,90;3456;16824;18,10;6951;30239;11,20;24800;97631;6,50;16984;85694;8,90;91172;409663;5,90;295994;1235083;3,10;0;0;5;501,50;103;13,50;124;11,20;1779;1,90;3427;2,70;9047;4,80;14698;13,80;46207;2,40;341728;2,90;OC 384 9703;; +24102;VENDREDI;20/12/2024;1;21/03/2025;14;10;50;33;21;6;9;-10-14-21-33-50-;-6-9-;0;0;0;0;3;222689,40;2;8;19517,30;12;47;1034,70;158;792;113,10;423;1908;49,60;359;1797;37,00;5729;27621;12,00;7555;35050;10,50;16238;77174;8,90;30960;142307;5,80;109002;496007;5,30;236494;1095328;3,80;0;0;5;549,40;79;19,30;205;7,40;2756;1,30;3693;2,70;14862;3,20;24997;8,90;52653;2,30;457311;2,40;FP 855 3646;; +24101;MARDI;17/12/2024;6;18/03/2025;1;21;4;29;3;7;2;-1-3-4-21-29-;-2-7-;1;1;77557137,00;0;1;603073,30;1;8;17618,50;14;48;914,60;242;923;87,60;554;2094;40,80;431;1749;34,30;7662;30297;9,90;9579;39664;8,40;17216;75380;8,20;37796;152149;4,90;117001;505740;4,70;222671;1002365;3,80;0;0;7;336,70;103;12,70;261;5,00;3438;,90;4415;1,90;16971;2,40;26465;7,20;54578;1,90;397402;2,30;MH 801 5430;; +24100;VENDREDI;13/12/2024;5;14/03/2025;25;1;42;15;50;10;4;-1-15-25-42-50-;-4-10-;0;0;0;0;1;880351,00;5;9;22861,30;13;46;1393,10;202;805;146,60;492;1775;70,30;571;2226;39,30;7843;26101;16,70;10446;37914;12,80;25572;94973;9,50;43352;143323;7,60;157494;556232;6,20;383582;1372521;4,00;0;0;7;670,50;96;27,10;259;10,00;3968;1,60;5264;3,30;21559;3,80;37666;10,10;78548;2,70;746151;2,50;GS 852 9356;; +24099;MARDI;10/12/2024;4;11/03/2025;29;25;5;47;24;9;5;-5-24-25-29-47-;-5-9-;0;0;0;0;1;554372,40;1;3;43188,60;1;30;1345,20;134;596;124,70;398;1816;43,20;248;1118;49,30;5556;27028;10,20;6282;28922;10,60;12658;56637;10,10;28333;136199;5,00;96682;434741;5,00;194905;859369;4,10;0;0;0;0;65;54,00;183;6,80;2586;1,20;2981;2,80;13318;2,90;19349;9,40;46023;2,20;363805;2,50;ES 665 1000;; +24098;VENDREDI;06/12/2024;3;07/03/2025;25;39;47;14;44;6;10;-14-25-39-44-47-;-6-10-;0;0;0;0;0;0;3;11;78150,90;4;39;1300,60;138;608;153,60;235;1415;69,80;335;1479;46,90;4348;20745;16,70;6223;28649;13,50;14702;69227;10,40;24112;112234;7,70;95001;426665;6,40;222339;1025701;4,30;0;0;3;978,10;75;21,70;120;13,50;2247;1,80;3056;3,50;12330;4,10;22032;10,80;47695;2,80;482184;2,40;LT 636 0771;; +24097;MARDI;03/12/2024;2;04/03/2025;46;23;34;25;47;6;8;-23-25-34-46-47-;-6-8-;0;0;0;0;0;0;1;5;129039,70;3;21;1812,80;68;443;158,30;239;1158;64,00;186;967;53,80;3919;17740;14,60;4527;21694;13,30;9470;46965;11,50;22670;100698;6,50;70597;338553;6,00;153855;736296;4,50;0;0;1;2152,20;30;39,80;102;11,70;1911;1,50;2304;3,40;11053;3,40;20097;8,60;35157;2,80;374020;2,30;AE 196 0600;; +24096;VENDREDI;29/11/2024;1;28/02/2025;8;18;43;17;29;5;9;-8-17-18-29-43-;-5-9-;0;0;0;0;4;171105,20;0;6;26660,00;8;46;1083,10;189;936;98,00;479;2459;39,40;465;1883;36,20;6683;32798;10,30;8626;39040;9,70;19660;81499;8,60;33348;164667;5,20;121497;545804;4,90;261622;1123038;3,80;0;0;4;692,50;92;16,70;217;7,00;3090;1,20;4030;2,50;15531;3,10;24221;9,20;57853;2,10;445996;2,50;CJ 789 1845;; +24095;MARDI;26/11/2024;15;25/02/2025;40;25;31;11;7;12;9;-7-11-25-31-40-;-9-12-;0;1;212448937,00;4;15;73659,10;2;14;18445,00;11;55;1462,40;354;1492;99,30;568;2400;65,20;802;3462;31,70;7616;33051;16,60;13598;57385;10,60;35112;144615;7,90;38931;168815;8,20;185250;781193;5,50;474219;1953020;3,50;0;0;5;910,90;150;16,80;267;9,40;3617;1,70;6415;2,60;18620;4,20;30529;12,10;85964;2,40;685309;2,60;WM 610 1340;; +24094;VENDREDI;22/11/2024;14;21/02/2025;12;10;29;11;31;11;8;-10-11-12-29-31-;-8-11-;0;0;0;3;11;151312,40;11;27;14407,60;28;93;1302,80;692;2182;102,20;1192;3916;60,20;1745;5351;30,90;17104;56732;14,60;28994;90710;10,10;71764;217100;7,90;85991;295013;7,00;385625;1246775;5,20;938271;2931777;3,60;2;2366,60;15;252,40;324;14,60;541;8,70;7837;1,50;13013;2,40;39422;3,70;59328;11,60;172961;2,20;1278305;2,60;GV 101 9014;F LWM 35935,F GWD 13213,F PHF 13460,F VZV 83429,F FXV 55986,F GMM 67357,F HSD 57137,F DJF 33286,F CFP 59552,F CGW 16564,F SZF 07542,F KNP 54102,F QLN 57442,F FHZ 79989,F CNP 17928,F PDH 28668,F KZG 04815,F QCD 50969,F VZW 90554,F GRF 26663,F HMR 37863,F QBR 56894,F HFP 80660,F BCR 25193,F FXW 82222,F TXV 15611,F FRK 44523,F QBS 00245,F RQN 82256,N CJK 47213,O LXQ 36780,W NPZ 41244,W DBV 47030,O FTL 28269,H QLJ 06109,T QKP 19167,V QKP 61496,J QKS 64141,M QLB 74455,V QKP 53077,T QLH 36037,J QKN 47246,X QKM 17444,V PJR 45966,Z QLD 76785,T QLB 99719,M QLF 27486,M QKW 06991,M PJQ 65682,J QLG 22716,Z QKX 82893,V QKG 63373,J QLH 15154,X QKZ 16818,V QLD 49200,T QKQ 32242,J QLD 02896,Z QLD 36160,H QKW 78778,R LTH 63481,R LTL 73909,R LTP 84743,R LTL 24947,R LTM 65259,E BCM 03824,E BBX 97858,E BBX 29007,E BCG 03652,E BFC 45227,E BCG 02594,E BBJ 26152,E BCT 01332,E BFJ 65972,E BFS 30806,E BDV 99400,E BFT 45043,E BCR 18727,P JPV 29109,P JVG 27108,P JJN 39681,P JRW 37687,P JPH 07932,P JNJ 24734,P JDZ 32757,P JGJ 36775,P JMM 00998,B WPS 07863,B WQH 59438,B WRB 94521,B WRG 36072,B ZZZ 66811,B WPJ 29140,B WPP 46396,S BCW 88348,S BDN 76665,S BDP 67456,S BDF 83348,L LRV 88951,I RKJ 86198,I RKS 16405; +24093;MARDI;19/11/2024;13;18/02/2025;28;20;49;13;4;7;12;-4-13-20-28-49-;-7-12-;0;0;0;0;2;437600,40;3;11;18595,30;10;54;1179,80;289;1110;105,70;496;2142;57,90;659;2307;37,70;6562;28642;15,20;11600;45762;10,60;25900;105496;8,50;32669;141623;7,70;153207;638935;5,40;342223;1464889;3,70;0;0;4;842,90;130;14,40;243;7,70;3081;1,50;5261;2,30;15419;3,80;24283;11,20;69310;2,20;541899;2,50;HH 153 7574;; +24092;VENDREDI;15/11/2024;12;14/02/2025;7;39;8;44;34;2;6;-7-8-34-39-44-;-2-6-;0;0;0;0;1;1030972,90;2;9;26772,80;15;51;1471,60;158;821;168,30;478;2331;62,60;357;1870;54,90;8960;38936;13,10;9391;45700;12,50;20192;100728;10,50;50428;215347;5,90;160103;723305;5,60;344042;1612654;4,00;0;0;4;977,20;72;30,10;211;10,20;3909;1,30;4306;3,30;21759;3,10;35141;9,00;72861;2,40;642356;2,40;DS 412 3521;; +24091;MARDI;12/11/2024;11;11/02/2025;11;12;50;10;22;10;1;-10-11-12-22-50-;-1-10-;0;0;0;0;3;256306,60;0;12;14975,70;10;41;1365,20;196;910;113,30;397;1827;59,60;533;2292;33,40;5525;24670;15,50;9048;39940;10,60;22955;98242;8,00;26399;119027;8,00;125041;545032;5,50;322931;1394060;3,50;0;0;6;477,20;86;18,40;185;8,50;2509;1,50;3987;2,60;12413;4,00;18180;12,70;56655;2,30;402520;2,80;TN 647 7615;; +24090;VENDREDI;08/11/2024;10;07/02/2025;48;2;35;33;42;1;3;-2-33-35-42-48-;-1-3-;0;0;0;0;3;316791,80;2;12;18509,80;11;39;1773,90;145;811;157,10;385;1816;74,10;375;1838;51,50;5426;27240;17,30;7970;38734;13,60;17317;87064;11,20;32730;157954;7,50;122912;594236;6,30;270606;1340773;4,50;0;0;6;587,90;75;26,10;202;9,70;2593;1,80;3896;3,30;15425;4,00;29558;9,60;57836;2,70;618200;2,30;RS 871 5067;; +24089;MARDI;05/11/2024;9;04/02/2025;50;8;37;1;19;9;6;-1-8-19-37-50-;-6-9-;0;0;0;1;6;119513,00;3;8;20949,10;10;44;1186,30;193;870;110,50;410;1882;54,00;453;1846;38,60;6004;27255;13,10;8113;35363;11,20;18140;80046;9,20;32599;146834;6,10;113765;501357;5,60;254391;1128830;4,00;0;0;6;466,50;84;18,50;170;9,10;2847;1,30;3692;2,80;14785;3,30;25513;8,90;52683;2,40;467139;2,40;HR 461 7160;; +24088;VENDREDI;01/11/2024;8;31/01/2025;44;7;50;33;38;2;7;-7-33-38-44-50-;-2-7-;0;0;0;0;5;152337,60;0;4;44504,70;7;50;1108,90;131;701;145,70;485;2297;47,00;292;1589;47,70;7612;33152;11,40;7349;39237;10,70;14339;77972;10,10;43770;184689;5,10;114310;567133;5,30;213391;1113984;4,30;0;0;5;546,70;61;24,80;226;6,70;3433;1,10;3371;3,00;19101;2,50;31887;6,90;52525;2,30;492047;2,20;AE 224 2371;; +24087;MARDI;29/10/2024;7;28/01/2025;45;30;19;1;2;12;10;-1-2-19-30-45-;-10-12-;0;0;0;1;2;293155,70;0;3;45676,80;4;23;1855,70;84;478;164,40;211;1027;80,90;254;1368;42,60;3152;15437;18,90;4661;22908;14,20;13913;65223;9,20;16212;81020;9,00;70188;342322;6,70;210847;984152;3,70;1;1190,20;3;317,40;38;31,30;91;13,00;1530;1,90;2288;3,40;8093;4,60;12898;13,40;34948;2,80;298332;2,90;MU 135 5274;; +24086;VENDREDI;25/10/2024;6;24/01/2025;4;20;45;25;17;9;8;-4-17-20-25-45-;-8-9-;0;0;0;0;2;402669,50;3;7;26888,70;10;40;1465,60;198;1014;106,50;330;1724;66,20;410;2069;38,70;4927;25930;15,40;9209;45365;9,80;19205;94572;8,80;24866;131896;7,60;124010;630402;5,00;263077;1321965;3,80;0;0;4;692,80;98;15,70;156;9,80;2386;1,60;4253;2,40;11849;4,00;18560;12,10;57058;2,20;454992;2,40;VP 603 1205;; +24085;MARDI;22/10/2024;5;21/01/2025;13;46;3;27;30;1;2;-3-13-27-30-46-;-1-2-;0;0;0;1;2;269163,30;4;9;13979,50;5;24;1632,80;156;705;102,30;226;1155;66,00;363;1695;31,60;3216;16432;16,30;6395;29417;10,10;14859;69726;7,90;16944;85273;7,90;87506;402090;5,20;196822;930745;3,60;1;1121,80;2;448,70;85;13,10;111;10,10;1490;1,80;2955;2,50;8018;4,40;13014;12,50;40539;2,20;324679;2,50;UK 403 4512;; +24084;VENDREDI;18/10/2024;4;17/01/2025;23;39;4;15;30;12;1;-4-15-23-30-39-;-1-12-;0;0;0;0;2;352631,70;0;6;27471,90;5;35;1466,80;140;706;133,90;332;1479;67,50;473;2170;32,30;4399;20021;17,50;6585;30918;12,60;20483;94852;7,60;23330;107220;8,20;92509;432047;6,40;272420;1256893;3,50;0;0;3;932,00;62;24,60;174;8,80;2238;1,70;3197;3,20;12077;4,00;19955;11,20;45500;2,70;391339;2,80;OJ 183 5872;; +24083;MARDI;15/10/2024;3;14/01/2025;15;48;2;36;32;3;9;-2-15-32-36-48-;-3-9-;0;0;0;0;1;506802,60;0;5;23689,60;7;27;1366,40;92;481;141,20;229;1221;58,80;206;1035;48,70;3904;19239;13,10;4079;21401;13,10;8801;45553;11,50;24215;113067;5,60;66251;328382;6,00;142398;692194;4,60;0;0;4;507,10;49;22,50;111;9,90;1906;1,40;2046;3,60;11439;3,00;22592;7,20;32549;2,80;362827;2,20;TF 309 7516;; +24082;VENDREDI;11/10/2024;2;10/01/2025;23;46;32;16;49;5;4;-16-23-32-46-49-;-4-5-;0;0;0;1;3;223088,00;0;6;26069,60;8;30;1624,00;163;738;121,60;320;1369;69,30;303;1437;46,30;4530;20133;16,50;6565;31194;11,90;13896;64209;10,70;25347;112915;7,40;99826;463749;5,60;206566;946037;4,40;0;0;5;567,80;82;19,20;158;9,80;2220;1,70;3113;3,30;12291;3,90;22724;10,00;47820;2,60;500129;2,20;JJ 233 7686;; +24081;MARDI;08/10/2024;1;07/01/2025;7;19;34;14;40;8;6;-7-14-19-34-40-;-6-8-;0;0;0;0;4;122519,20;0;7;16362,70;7;31;1150,80;116;533;123,30;314;1313;52,90;269;1279;38,10;4572;19378;12,50;5650;25616;10,60;12548;56697;8,90;24137;100095;6,10;84914;372862;5,10;186586;821666;3,70;0;0;3;687,50;68;16,80;145;7,90;2122;1,30;2701;2,80;11308;3,10;17947;9,20;40727;2,20;335665;2,40;WM 303 1045;; +24080;VENDREDI;04/10/2024;8;03/01/2025;4;17;8;16;20;6;1;-4-8-16-17-20-;-1-6-;0;1;162256512,00;5;12;94700,00;10;20;13279,70;30;97;852,80;528;1811;84,10;972;3444;46,70;1306;4613;24,50;11722;43110;13,10;18900;68777;9,10;45349;167492;7,00;52545;204256;6,90;230953;870340;5,10;540775;2086207;3,40;3;927,40;16;139,10;221;12,50;434;6,30;5128;1,30;8415;2,10;23718;3,60;36316;11,00;104618;2,10;757309;2,60;NR 671 2640;; +24079;MARDI;01/10/2024;7;31/12/2024;22;41;8;42;29;9;11;-8-22-29-41-42-;-9-11-;0;0;0;1;7;124015,80;2;10;20289,10;8;46;1373,80;185;846;137,60;380;1791;68,70;527;2087;41,40;5804;26227;16,40;9497;40760;11,80;22997;93991;9,50;31735;141713;7,60;139618;587531;5,80;336913;1358844;4,00;1;2060,60;2;824,20;79;25,70;173;11,80;2717;1,80;4307;3,10;14875;4,20;25464;11,60;64203;2,60;572723;2,50;LE 141 0047;; +24078;VENDREDI;27/09/2024;6;27/12/2024;7;21;38;20;27;12;5;-7-20-21-27-38-;-5-12-;0;0;0;1;3;416328,80;7;18;16217,10;17;68;1337,00;415;1478;113,30;795;2773;63,80;1130;3682;33,70;10891;39401;15,70;18595;67092;10,30;48235;164809;7,80;54133;199805;7,80;258915;940455;5,20;649782;2273779;3,40;1;3397,30;7;388,20;187;17,80;378;8,90;5132;1,60;8527;2,60;26564;3,90;40847;11,90;120278;2,30;900632;2,70;AQ 201 0796;; +24077;MARDI;24/09/2024;5;24/12/2024;20;21;49;36;18;5;3;-18-20-21-36-49-;-3-5-;0;0;0;2;4;138661,10;2;2;64814,70;6;25;1615,00;96;535;139,00;324;1429;55,00;255;1096;50,40;4936;22528;12,20;5143;24809;12,40;11230;51717;11,00;28367;129548;5,30;87643;400108;5,40;175531;793593;4,40;1;1219,20;3;325,10;42;29,00;150;8,00;2228;1,30;2439;3,30;12649;3,00;21363;8,20;40884;2,40;377496;2,30;KD 820 4282;; +24076;VENDREDI;20/09/2024;4;20/12/2024;16;37;34;25;29;3;7;-16-25-29-34-37-;-3-7-;0;0;0;1;3;231778,90;1;13;12500,90;12;54;937,30;147;752;123,90;572;2608;37,70;302;1537;45,00;8476;37366;9,20;7628;34527;11,10;14313;68306;10,50;48495;207694;4,10;113129;502378;5,40;208871;994624;4,40;1;1525,20;3;305,00;61;24,20;242;6,20;3766;1,00;3578;2,80;20937;2,20;37192;5,90;52149;2,30;503672;2,10;GK 429 0112;; +24075;MARDI;17/09/2024;3;17/12/2024;30;32;20;41;44;10;1;-20-30-32-41-44-;-1-10-;0;0;0;0;0;0;1;4;159071,50;5;19;1976,00;97;460;150,30;213;917;79,70;215;1158;44,30;3085;13868;18,50;4528;20774;13,70;10445;49650;10,70;16142;73056;8,80;64649;296919;6,80;163134;752833;4,30;0;0;2;1055,90;42;27,90;100;11,60;1536;1,80;2181;3,50;8004;4,50;14555;11,60;31151;3,00;324241;2,60;QG 873 4967;; +24074;VENDREDI;13/09/2024;2;13/12/2024;31;42;10;15;17;4;12;-10-15-17-31-42-;-4-12-;0;0;0;1;3;252503,60;0;5;35408,50;7;23;2397,50;207;749;135,60;438;1516;70,80;638;2144;35,10;6143;21197;17,70;10614;35525;11,80;27791;94256;8,30;31206;111292;8,50;149577;509083;5,80;390054;1330865;3,60;0;0;3;1329,80;97;22,80;229;9,50;3206;1,70;5189;2,80;16316;4,20;26132;12,20;72758;2,40;591003;2,70;TE 497 3637;; +24073;MARDI;10/09/2024;1;10/12/2024;48;46;6;29;47;9;2;-6-29-46-47-48-;-2-9-;0;0;0;0;1;484860,80;2;6;18886,60;3;13;2715,10;77;357;182,10;192;954;72,00;151;811;59,50;3424;15401;15,60;3844;18241;14,70;8378;40170;12,40;20745;93391;6,50;62495;290184;6,50;135782;629021;4,80;0;0;2;998,50;42;26,40;89;12,40;1656;1,60;1964;3,70;9674;3,50;17740;9,00;30927;2,90;351858;2,20;QN 829 8322;; +24072;VENDREDI;06/09/2024;12;06/12/2024;34;47;14;12;41;3;4;-12-14-34-41-47-;-3-4-;0;1;149017789,00;1;4;257427,90;1;7;34380,00;14;55;1362,90;210;1022;135,10;434;2111;69,10;468;2061;49,70;7002;31655;16,20;10530;46435;12,30;21629;96179;11,00;39884;175554;7,30;168968;717046;5,60;337735;1470121;4,40;0;0;10;421,60;90;25,70;184;12,40;3147;1,80;4715;3,20;17890;4,00;30057;11,20;76083;2,50;739666;2,20;GS 272 9213;; +24071;MARDI;03/09/2024;11;03/12/2024;7;9;11;45;16;5;2;-7-9-11-16-45-;-2-5-;0;0;0;0;3;262554,00;2;11;16735,40;16;55;1042,50;238;1014;104,10;714;2788;40,00;600;2309;33,90;8754;37098;10,50;12070;48894;8,90;25955;102946;7,90;41274;178225;5,50;159631;663338;4,60;333091;1378289;3,60;0;0;7;447,10;109;15,80;310;5,50;3640;1,10;5293;2,10;17851;3,00;26491;9,40;70351;2,00;492448;2,50;MS 437 2213;; +24070;VENDREDI;30/08/2024;10;29/11/2024;3;24;42;33;27;6;4;-3-24-27-33-42-;-4-6-;0;0;0;2;5;184395,80;4;14;15391,50;16;48;1398,20;271;1148;107,60;673;2702;48,30;513;2523;36,40;9163;37184;12,30;10823;50222;10,10;23976;111392;8,50;45826;189326;6,10;147363;687522;5,20;330448;1536840;3,80;0;0;6;572,10;114;16,10;292;6,40;4009;1,10;4771;2,60;20136;2,90;31748;8,60;65768;2,30;542203;2,50;IX 551 1819;; +24069;MARDI;27/08/2024;9;26/11/2024;1;47;42;8;11;11;4;-1-8-11-42-47-;-4-11-;0;0;0;0;0;0;1;5;160042,00;6;36;1311,50;134;626;138,90;294;1394;65,90;295;1368;47,20;4534;20589;15,60;6701;29083;12,30;15577;66704;10,00;23087;108715;7,40;100021;444203;5,70;239668;1036127;3,90;0;0;5;500,90;66;21,00;135;10,10;2098;1,60;3053;3,00;10536;4,10;16599;12,00;45861;2,40;372929;2,60;IS 628 8428;; +24068;VENDREDI;23/08/2024;8;22/11/2024;50;47;24;9;15;8;9;-9-15-24-47-50-;-8-9-;0;0;0;1;1;778922,80;1;7;26006,70;5;28;2025,10;139;799;130,70;337;1669;66,10;339;1604;48,30;5112;24219;16,00;7879;37459;11,50;16036;76049;10,50;27535;129475;7,50;122697;568176;5,40;252162;1163902;4,20;0;0;4;744,30;75;21,70;161;10,20;2423;1,60;3653;3,00;12715;4,00;20089;11,80;55533;2,40;498064;2,30;IZ 803 0758;; +24067;MARDI;20/08/2024;7;19/11/2024;10;18;13;26;7;12;3;-7-10-13-18-26-;-3-12-;0;0;0;3;8;71422,40;5;17;7855,30;20;50;831,80;317;1155;66,30;345;1615;50,10;811;2802;20,30;4386;19267;14,70;10370;41344;7,60;25568;98103;6,00;20141;90712;7,80;114373;483099;4,60;280515;1152657;3,10;2;621,40;5;198,80;136;9,00;171;7,20;2153;1,40;4713;1,70;9752;3,90;14090;12,70;52368;1,90;322681;2,70;DV 355 7712;; +24066;VENDREDI;16/08/2024;6;15/11/2024;49;45;15;29;17;10;1;-15-17-29-45-49-;-1-10-;0;0;0;1;4;184603,50;0;5;34515,90;3;29;1853,50;109;663;149,30;278;1352;77,40;316;1744;42,10;3605;20538;17,90;5638;30884;13,20;15472;82155;9,20;19391;106693;8,60;84925;459663;6,30;227238;1208658;3,80;0;0;2;1234,50;40;34,20;129;10,50;1795;1,80;2748;3,30;9796;4,30;16286;12,10;40627;2,70;366466;2,60;EW 999 5827;; +24065;MARDI;13/08/2024;5;12/11/2024;15;16;40;47;39;1;6;-15-16-39-40-47-;-1-6-;0;0;0;1;1;534410,60;0;3;41633,50;6;26;1496,20;82;420;170,60;204;1048;72,20;175;977;54,40;3095;15680;16,90;3879;20234;14,60;9020;47407;11,60;18596;89519;7,40;63435;319528;6,60;146775;756665;4,40;0;0;4;504,00;37;29,40;97;11,40;1475;1,80;1948;3,80;8568;4,00;15916;10,10;30345;3,00;335340;2,40;QR 386 7838;; +24064;VENDREDI;09/08/2024;4;08/11/2024;33;23;25;44;21;4;10;-21-23-25-33-44-;-4-10-;0;0;0;0;4;163953,00;2;6;25545,60;6;33;1446,70;187;844;104,10;329;1481;62,70;409;1836;35,50;4561;21243;15,30;7059;33542;10,80;17935;87750;7,70;24084;108813;7,50;95028;454827;5,60;242121;1148588;3,60;0;0;2;1278,30;80;17,30;161;8,80;2272;1,50;3363;2,70;11716;3,70;18842;10,80;45139;2,50;383535;2,60;LP 008 3446;; +24063;MARDI;06/08/2024;3;05/11/2024;41;27;50;1;18;2;12;-1-18-27-41-50-;-2-12-;0;0;0;2;4;121482,30;4;7;16224,20;4;23;1538,00;96;489;133,20;164;881;78,10;249;1214;39,80;2685;13024;18,50;4383;21234;12,70;11069;51760;9,70;14052;68138;8,90;64234;305973;6,20;161788;759216;4,00;0;0;0;0;53;56,10;89;11,40;1346;1,90;2108;3,30;7170;4,60;12838;11,90;31515;2,70;292735;2,60;NX 605 8090;; +24062;VENDREDI;02/08/2024;2;01/11/2024;12;7;33;5;46;3;12;-5-7-12-33-46-;-3-12-;0;0;0;0;3;204919,70;6;14;10262,80;10;32;1398,50;189;817;100,90;283;1359;64,10;454;1882;32,50;4140;19020;16,00;8327;34379;9,90;19459;79750;7,90;20075;95045;8,10;105823;462879;5,20;253022;1092703;3,50;0;0;5;484,80;94;14,10;132;10,10;2144;1,50;4056;2,20;10465;4,00;15196;12,70;50942;2,10;356162;2,70;PI 666 8268;; +24061;MARDI;30/07/2024;1;29/10/2024;28;37;9;38;25;2;8;-9-25-28-37-38-;-2-8-;0;0;0;0;0;0;0;5;108423,60;8;23;1390,70;96;436;135,10;264;1224;50,80;151;831;52,60;4533;20011;10,90;4373;20354;11,90;9202;44479;10,20;23517;108011;5,00;67488;311867;5,50;144571;671590;4,10;0;0;4;434,30;52;18,50;115;8,20;2079;1,10;2094;3,00;10850;2,70;17873;7,80;32074;2,40;284465;2,40;LO 604 7342;; +24060;VENDREDI;26/07/2024;2;25/10/2024;35;37;23;19;4;8;4;-4-19-23-35-37-;-4-8-;0;1;28649442,00;1;4;152025,20;1;10;14212,30;13;54;819,70;187;902;90,40;427;1966;43,80;334;1741;34,70;5826;27669;10,90;6634;34471;9,80;13972;71010;8,80;29718;140022;5,40;94055;459322;5,20;200224;975900;3,90;1;1262,40;7;144,20;86;14,60;232;5,30;2727;1,10;3051;2,70;13219;2,90;19942;9,10;43950;2,30;358960;2,50;IX 804 7766;; +24059;MARDI;23/07/2024;1;22/10/2024;8;10;16;34;4;8;4;-4-8-10-16-34-;-4-8-;0;0;0;0;3;147222,00;2;7;14746,30;16;76;423,00;160;630;94,00;704;2862;21,80;279;1240;35,40;5911;25377;8,60;6480;28739;8,50;11984;54425;8,30;21387;97862;5,60;80962;359529;4,80;162812;731173;3,80;0;0;4;438,50;70;13,90;308;3,10;2640;,90;3030;2,10;9928;3,00;14568;9,60;38556;2,00;266844;2,60;ET 230 6121;; +24058;VENDREDI;19/07/2024;4;18/10/2024;44;22;35;48;15;6;7;-15-22-35-44-48-;-6-7-;1;2;26181999,00;1;3;240060,30;0;3;56106,00;5;42;1248,20;173;851;113,40;290;1582;64,50;359;1790;40,00;4479;23538;15,20;7517;36053;11,00;13309;71874;10,30;25825;128931;6,90;102969;513956;5,50;189547;1033719;4,40;1;1428,50;2;571,40;83;17,00;132;10,80;2082;1,70;3651;2,50;12000;3,70;21767;9,40;48324;2,40;491593;2,00;FB 529 4041;; +24057;MARDI;16/07/2024;3;15/10/2024;39;35;2;36;32;7;8;-2-32-35-36-39-;-7-8-;0;0;0;0;1;492731,30;2;5;23031,80;3;18;1992,70;76;371;178,00;173;859;81,30;154;795;61,70;2762;13715;17,80;3859;18872;14,50;7417;39280;12,90;17264;80470;7,60;63009;309957;6,20;122088;619066;5,00;0;0;0;0;41;75,00;94;11,20;1368;1,90;1941;3,70;8392;4,00;16908;9,40;30928;2,90;397607;1,90;DJ 920 2664;; +24056;VENDREDI;12/07/2024;2;11/10/2024;12;39;24;25;18;10;8;-12-18-24-25-39-;-8-10-;0;0;0;2;3;211029,30;2;8;18495,30;9;36;1280,10;154;743;114,20;332;1383;64,80;377;1835;34,30;4642;19646;16,00;7564;33345;10,50;19005;82980;7,80;22458;99530;7,90;103086;455333;5,40;258012;1112596;3,60;0;0;4;639,90;82;17,10;161;8,70;2239;1,50;3601;2,60;11341;3,90;17821;11,50;49322;2,30;380627;2,60;DO 972 0751;; +24055;MARDI;09/07/2024;1;08/10/2024;28;15;19;39;6;7;11;-6-15-19-28-39-;-7-11-;0;0;0;0;2;233146,70;2;7;15568,60;9;34;998,30;128;607;103,00;248;1213;54,40;310;1410;32,90;3679;16910;13,70;5986;25585;10,10;13334;57756;8,30;18709;86685;6,70;82314;352051;5,20;179259;792341;3,70;0;0;5;384,30;59;18,00;120;8,70;1930;1,30;2960;2,30;9344;3,50;15060;10,20;40341;2,10;315615;2,40;CC 517 0755;; +24054;VENDREDI;05/07/2024;3;04/10/2024;29;47;31;13;11;11;1;-11-13-29-31-47-;-1-11-;0;1;39132032,00;1;6;109598,20;3;14;10977,80;12;45;1063,70;175;847;104,10;332;1591;58,50;489;2056;31,80;4383;20981;15,60;7226;32900;11,10;19316;83957;8,10;21403;100491;8,10;99839;448978;5,70;264334;1150742;3,60;1;1497,20;7;171,10;82;18,20;172;8,60;2255;1,60;3449;2,80;10833;4,30;16770;12,90;48787;2,50;380840;2,80;PV 199 2903;; +24053;MARDI;02/07/2024;2;01/10/2024;46;7;2;34;35;6;8;-2-7-34-35-46-;-6-8-;0;0;0;0;2;248851,10;0;2;58160,60;3;21;1725,20;69;396;168,50;200;1009;69,90;177;972;51,00;3766;16589;14,90;4339;20357;13,50;9964;46192;11,10;22727;96176;6,40;72110;327200;6,00;163473;735440;4,30;0;0;1;2091,30;47;24,20;96;11,80;1841;1,50;2143;3,50;10658;3,30;19078;8,80;35409;2,60;352280;2,30;FG 761 0262;; +24052;VENDREDI;28/06/2024;1;27/09/2024;10;18;35;16;22;10;1;-10-16-18-22-35-;-1-10-;0;0;0;1;1;653225,30;2;5;30533,90;6;25;1902,10;142;617;141,90;347;1401;66,00;421;1847;35,20;4540;19080;17,00;7709;31109;11,60;19614;82728;8,10;22698;96503;8,40;105774;436567;5,90;272123;1139040;3,60;0;0;5;565,10;67;23,00;167;9,40;2320;1,60;3816;2,70;11754;4,10;17804;12,70;51593;2,40;400547;2,80;FO 577 0795;; +24051;MARDI;25/06/2024;12;24/09/2024;14;16;49;45;37;5;7;-14-16-37-45-49-;-5-7-;0;1;213887390,00;1;8;141110,20;2;15;17589,20;17;55;1494,10;299;1122;134,90;720;2697;59,30;621;2296;48,90;11967;44842;12,50;12947;49896;12,50;26600;103586;11,20;70237;260815;5,40;198955;764555;5,80;400999;1553498;4,60;1;2840,80;9;227,20;144;19,10;306;9,20;5221;1,30;5947;3,10;30255;2,90;56396;7,20;89653;2,50;916666;2,20;EJ 424 5315;; +24050;VENDREDI;21/06/2024;11;20/09/2024;4;3;11;7;17;12;3;-3-4-7-11-17-;-3-12-;0;0;0;7;14;86983,10;17;44;6468,40;15;74;1197,90;874;3055;53,40;853;3238;53,30;1869;6419;18,80;10769;40818;14,80;25738;91280;7,40;58429;204607;6,10;50365;198681;7,60;279392;1030776;4,60;652967;2392296;3,20;4;763,90;5;488,90;388;7,70;399;7,50;5072;1,40;11520;1,70;24114;3,90;35340;12,40;125987;1,90;807307;2,70;UP 370 6361;; +24049;MARDI;18/06/2024;10;17/09/2024;34;11;36;33;3;12;1;-3-11-33-34-36-;-1-12-;0;0;0;1;5;185067,00;2;6;36044,30;9;41;1642,90;192;800;155,10;407;1713;76,50;435;2029;45,40;6427;26555;17,30;8595;36552;14,00;23455;101523;9,40;35582;143991;8,00;133713;553256;6,60;369455;1534807;3,80;0;0;5;793,20;93;23,10;187;11,40;3118;1,70;3961;3,60;16700;4,00;28662;11,00;61919;2,80;568476;2,70;DO 202 2964;; +24048;VENDREDI;14/06/2024;9;13/09/2024;13;2;16;24;32;7;1;-2-13-16-24-32-;-1-7-;0;0;0;1;10;109895,20;3;19;13518,00;28;88;909,00;421;1604;91,80;945;3325;46,80;968;3742;29,20;11825;42911;12,70;16671;64389;9,40;35513;142255;7,90;59259;214237;6,40;217455;848501;5,10;452869;1847351;3,70;1;2613,90;11;190,10;200;12,80;413;6,20;5248;1,20;7349;2,30;26643;3,00;41603;9,00;97583;2,10;764806;2,40;OA 601 5773;; +24047;MARDI;11/06/2024;8;10/09/2024;48;15;7;34;45;7;9;-7-15-34-45-48-;-7-9-;0;0;0;0;5;168413,40;1;10;19680,50;14;56;1094,60;206;945;119,40;560;2360;50,50;361;1867;44,90;8638;34664;12,00;9302;41280;11,30;18712;82326;10,50;47821;187141;5,60;142692;616187;5,30;273912;1209516;4,40;0;0;4;864,90;93;20,60;256;7,40;3905;1,20;4318;2,90;21473;2,70;35458;7,80;64929;2,40;598955;2,30;OA 484 4606;; +24046;VENDREDI;07/06/2024;7;06/09/2024;30;26;15;37;16;8;5;-15-16-26-30-37-;-5-8-;0;0;0;2;9;136127,50;0;5;57267,40;18;79;1128,90;322;1312;125,20;1031;3821;45,40;790;2910;41,90;15001;56573;10,70;16262;60826;11,10;34796;127695;9,90;77437;294555;5,20;237909;885563;5,40;513976;1876170;4,10;1;3295,50;6;376,60;144;22,40;461;7,00;6550;1,20;7295;2,90;34566;2,90;56577;8,30;108329;2,40;957411;2,40;ES 120 1943;; +24045;MARDI;04/06/2024;6;03/09/2024;43;7;6;9;14;4;3;-6-7-9-14-43-;-3-4-;0;0;0;1;2;279488,30;1;7;18663,10;6;26;1565,00;154;647;115,80;352;1608;49,20;356;1443;38,50;4478;20966;13,20;7950;33014;9,40;16201;67722;8,50;21321;100326;6,90;110094;473384;4,60;218517;948932;3,70;1;1219,80;3;325,20;67;18,20;145;8,20;2057;1,40;3707;2,10;9842;3,80;14975;11,70;50444;1,90;358570;2,40;AU 243 4895;; +24044;VENDREDI;31/05/2024;5;30/08/2024;16;33;4;34;7;8;7;-4-7-16-33-34-;-7-8-;0;0;0;0;2;351238,00;3;3;54726,70;7;29;1763,30;167;828;113,70;358;1741;57,10;318;1539;45,40;5271;24339;14,30;9157;41733;9,30;15832;76287;9,50;28080;126555;6,90;131896;591308;4,60;232988;1102388;4,00;0;0;4;680,60;76;19,90;158;9,50;2435;1,50;4131;2,40;13041;3,60;19342;11,20;60028;2,00;475244;2,20;SA 414 4481;; +24043;MARDI;28/05/2024;4;27/08/2024;18;16;35;41;36;6;7;-16-18-35-36-41-;-6-7-;0;0;0;0;2;250693,20;2;12;9765,10;1;15;2433,20;97;513;131,00;184;973;73,00;173;928;53,80;3119;15394;16,20;4281;21209;13,10;8167;42788;12,10;18466;87237;7,20;68672;332149;5,90;130503;668155;4,70;0;0;1;1917,40;49;21,30;92;11,50;1486;1,70;2033;3,40;8663;3,80;16483;9,30;32581;2,60;372484;2,00;UM 326 7642;; +24042;VENDREDI;24/05/2024;3;23/08/2024;12;9;22;18;50;1;3;-9-12-18-22-50-;-1-3-;0;0;0;0;2;321429,70;0;6;25041,10;5;29;1613,70;212;803;107,30;424;1757;51,80;455;1760;36,30;5783;24731;12,90;8924;37089;9,60;19158;79991;8,30;26952;120936;6,60;119936;512888;4,90;257010;1114599;3,60;0;0;2;1251,90;93;14,70;174;7,90;2685;1,20;4091;2,20;12401;3,50;18285;10,90;54552;2,00;389871;2,50;OR 724 4652;; +24041;MARDI;21/05/2024;2;20/08/2024;13;11;48;14;34;9;7;-11-13-14-34-48-;-7-9-;0;0;0;0;1;461188,20;0;5;21557,40;4;33;1017,30;85;447;138,30;283;1264;51,70;167;919;49,90;4884;20234;11,30;4970;22989;11,10;9593;45943;10,30;26662;109565;5,20;79784;362186;5,00;151068;707727;4,10;0;0;2;603,60;33;29,50;130;7,60;2247;1,10;2404;2,70;12506;2,50;18497;7,80;37317;2,20;308461;2,30;HE 712 2807;; +24040;VENDREDI;17/05/2024;1;16/08/2024;31;41;46;32;18;10;1;-18-31-32-41-46-;-1-10-;0;0;0;0;2;306136,50;2;3;47699,40;4;25;1782,80;101;437;187,80;196;1009;86,00;222;1108;55,00;3185;14760;20,60;4573;21945;15,50;11118;54773;11,50;18186;83136;9,20;70868;333262;7,20;180744;853890;4,50;0;0;2;1262,50;51;27,50;93;15,00;1603;2,10;2353;3,90;9147;4,70;17174;11,80;35142;3,20;395702;2,50;HI 545 7650;; +24039;MARDI;14/05/2024;4;13/08/2024;35;2;8;28;17;7;9;-2-8-17-28-35-;-7-9-;1;1;48748900,00;2;5;104539,90;0;7;17451,90;9;43;884,90;196;856;81,80;411;1704;43,40;363;1704;30,50;5580;23530;11,00;7408;32287;8,90;14740;66637;8,10;28854;120656;5,40;95907;429438;4,80;186875;858175;3,80;0;0;3;675,70;96;11,70;201;5,50;2523;1,10;3441;2,10;13190;2,60;21604;7,50;44174;2,00;345249;2,30;LH 133 4150;; +24038;VENDREDI;10/05/2024;3;09/08/2024;29;44;13;48;28;12;4;-13-28-29-44-48-;-4-12-;0;0;0;0;3;213998,10;1;6;25007,40;4;18;2596,40;124;629;136,80;216;1241;73,30;273;1499;42,60;3561;18174;17,50;5857;28133;12,60;15383;72372;9,10;18052;93179;8,60;87571;415055;6,10;229484;1067762;3,80;0;0;2;1206,20;56;23,90;120;11,10;1864;1,70;2917;3,00;9321;4,40;15121;12,80;42222;2,50;355562;2,70;RW 693 7088;; +24037;MARDI;07/05/2024;2;06/08/2024;45;35;42;36;41;11;6;-35-36-41-42-45-;-6-11-;0;0;0;0;0;0;1;3;202575,50;7;21;1707,60;73;346;190,90;181;841;83,00;180;804;61,00;2952;13572;18,00;3512;16490;16,50;8195;38152;13,30;17414;77930;7,90;59286;272183;7,10;137564;627708;4,90;0;0;3;705,60;42;28,00;86;13,60;1504;1,90;1784;4,30;8816;4,10;17284;9,80;29977;3,20;354331;2,30;PJ 884 6301;; +24036;VENDREDI;03/05/2024;1;02/08/2024;6;49;9;30;10;4;3;-6-9-10-30-49-;-3-4-;0;0;0;1;3;211741,00;3;8;18557,70;6;24;1926,70;194;816;104,30;313;1569;57,30;424;1610;39,30;4871;21715;14,50;9083;35764;9,80;18381;75020;8,70;23355;109236;7,20;122479;508536;4,90;240546;1027342;3,90;0;0;3;852,60;87;16,30;157;8,80;2263;1,50;4222;2,20;11030;4,00;17244;11,90;57181;2,00;432283;2,30;RH 550 1796;; +24035;MARDI;30/04/2024;13;30/07/2024;24;47;13;22;33;5;1;-13-22-24-33-47-;-1-5-;1;1;166790050,00;1;8;114649,10;3;13;16489,40;15;68;981,80;329;1385;88,80;597;2517;51,60;788;3144;29,00;7334;33591;13,60;11623;51784;9,80;27729;117948;8,00;36606;165534;6,90;148899;688604;5,20;346843;1549560;3,70;0;0;8;442,40;142;13,80;240;8,00;3229;1,40;5290;2,40;16166;3,70;26516;10,60;66947;2,30;551022;2,50;FQ 403 9499;; +24034;VENDREDI;26/04/2024;12;26/07/2024;47;2;39;20;40;8;4;-2-20-39-40-47-;-4-8-;0;0;0;1;2;560301,50;2;11;23809,30;9;46;1773,40;204;987;152,20;498;2421;65,60;423;2238;49,80;8126;39302;14,20;9226;46215;13,40;20513;103414;11,20;48420;227046;6,10;144966;710644;6,20;325515;1579596;4,50;1;2319,20;0;0;100;40,90;201;11,20;3600;1,50;4252;3,60;21185;3,40;39963;8,30;66150;2,80;701841;2,30;QG 398 5688;; +24033;MARDI;23/04/2024;11;23/07/2024;9;49;11;6;32;2;10;-6-9-11-32-49-;-2-10-;0;0;0;1;4;195277,70;2;8;22819,80;8;34;1672,40;177;800;130,90;401;1759;62,90;424;1927;40,30;6058;25779;15,00;8857;37357;11,60;21371;90637;8,90;31610;135438;7,20;125591;535723;5,70;304180;1305450;3,80;1;1694,90;2;677,90;76;22,00;159;10,20;2792;1,40;3969;2,80;14542;3,60;24154;10,00;56697;2,40;471953;2,50;JX 323 5847;; +24032;VENDREDI;19/04/2024;10;19/07/2024;10;20;46;40;44;1;3;-10-20-40-44-46-;-1-3-;0;0;0;0;1;938778,20;3;11;19946,10;10;39;1752,30;161;918;137,10;416;1995;66,70;401;2069;45,10;5962;28576;16,30;9101;44084;11,80;19483;96956;10,00;34069;160692;7,30;126927;604285;6,10;283118;1364068;4,30;0;0;4;865,20;80;24,00;185;10,20;2670;1,70;4165;3,00;15118;3,90;26406;10,40;57728;2,70;579645;2,30;DC 834 9863;; +24031;MARDI;16/04/2024;9;16/07/2024;39;31;46;29;22;3;7;-22-29-31-39-46-;-3-7-;0;0;0;0;2;337636,50;1;5;31564,40;12;44;1117,20;128;601;150,60;470;1918;49,90;296;1451;46,30;7612;31921;10,50;6795;29654;12,60;13474;61758;11,30;45670;188726;4,40;106324;460357;5,70;208837;940834;4,50;0;0;6;444,50;55;26,90;215;6,70;3257;1,10;3086;3,10;19315;2,30;35979;5,90;47684;2,50;487441;2,10;MX 883 5599;; +24030;VENDREDI;12/04/2024;8;12/07/2024;45;16;12;2;3;11;2;-2-3-12-16-45-;-2-11-;0;0;0;1;2;389492,20;2;8;22757,60;7;37;1532,60;178;847;123,30;377;1862;59,30;418;1841;42,10;5710;26529;14,60;8849;39506;10,90;20721;89196;9,00;29497;138497;7,00;125186;566236;5,40;300704;1318782;3,70;0;0;3;985,10;86;18,80;177;9,10;2790;1,40;4115;2,60;14232;3,50;22051;10,70;58437;2,20;436542;2,60;OU 073 1106;; +24029;MARDI;09/04/2024;7;09/07/2024;27;46;19;23;26;2;10;-19-23-26-27-46-;-2-10-;0;0;0;0;0;0;1;5;143308,70;7;27;1565,90;113;578;134,70;284;1290;63,80;275;1382;41,80;4492;19907;14,50;6592;29808;10,80;15275;69641;8,60;22851;102086;7,10;93444;427979;5,30;218420;1007228;3,60;0;0;3;735,70;58;20,40;136;8,90;2146;1,40;3070;2,60;10772;3,50;16418;10,70;43187;2,30;333463;2,60;AR 193 8181;; +24028;VENDREDI;05/04/2024;6;05/07/2024;26;37;35;18;13;11;8;-13-18-26-35-37-;-8-11-;0;0;0;0;2;364537,40;4;13;13107,40;11;41;1294,40;172;790;123,70;285;1521;67,90;422;1804;40,20;4550;22405;16,20;7366;34693;11,60;18244;81880;9,20;23842;116993;7,80;107490;500313;5,70;268386;1187042;3,90;0;0;7;410,70;69;22,80;151;10,50;2171;1,80;3438;3,00;11671;4,20;18971;12,10;50572;2,50;434232;2,60;IX 755 5449;; +24027;MARDI;02/04/2024;5;02/07/2024;36;1;48;23;31;8;5;-1-23-31-36-48-;-5-8-;0;0;0;0;0;0;0;2;311771,80;2;24;1533,00;99;406;166,90;268;1272;56,30;176;893;56,30;4322;20899;12,00;4415;21138;13,20;9778;45917;11,30;25532;122707;5,10;69599;332177;6,00;151562;711622;4,50;0;0;1;1950,90;39;27,70;114;9,50;1927;1,30;2091;3,40;11244;3,00;20419;7,60;32702;2,70;329918;2,30;KE 998 1888;; +24026;VENDREDI;29/03/2024;4;28/06/2024;49;35;36;17;16;10;1;-16-17-35-36-49-;-1-10-;0;0;0;0;1;687795,50;0;6;26791,50;8;24;2086,20;113;588;156,80;199;1135;85,90;265;1289;53,10;3464;17335;19,70;5293;25345;15,00;12748;63117;11,20;20427;96601;8,90;82216;390707;6,90;212892;1018156;4,20;0;0;6;459,70;70;21,50;110;13,50;1771;2,10;2688;3,70;10381;4,60;18286;12,10;40732;3,00;418300;2,60;NU 277 4690;; +24025;MARDI;26/03/2024;3;25/06/2024;14;26;29;13;2;5;6;-2-13-14-26-29-;-5-6-;0;0;0;1;5;99867,40;2;5;23340,60;9;38;956,50;214;843;79,40;300;1400;50,50;356;1676;29,60;3844;19001;13,00;7663;33176;8,30;15112;65640;7,80;18686;93205;6,70;95958;427543;4,60;189149;850599;3,70;0;0;3;634,90;88;12,00;135;7,60;1771;1,40;3502;2,00;8913;3,60;13329;11,40;43984;1,90;308988;2,40;FM 038 5979;; +24024;VENDREDI;22/03/2024;2;21/06/2024;32;8;44;11;23;9;10;-8-11-23-32-44-;-9-10-;0;0;0;0;12;54013,30;8;28;5410,20;10;37;1275,20;284;1226;70,80;271;1275;72,00;688;2946;21,90;3603;17390;18,50;8470;38488;9,30;21146;93918;7,10;18745;89886;9,00;108420;483588;5,20;267569;1176361;3,50;0;0;8;329,30;144;10,00;162;8,90;1821;1,90;4017;2,40;9570;4,70;15632;13,50;51998;2,30;408166;2,50;KP 843 6737;; +24023;MARDI;19/03/2024;1;18/06/2024;32;18;7;20;16;1;3;-7-16-18-20-32-;-1-3-;0;0;0;3;3;156244,20;2;6;18258,40;8;23;1483,50;142;648;97,00;284;1238;53,60;289;1401;33,30;3937;17254;13,50;6128;26685;9,70;13424;59872;8,00;19564;87492;6,70;83748;365158;5,00;180753;803809;3,70;2;528,00;4;211,20;68;15,50;127;8,30;1829;1,40;2834;2,40;8983;3,60;13791;11,00;38968;2,20;300304;2,50;HZ 468 3439;; +24022;VENDREDI;15/03/2024;7;14/06/2024;31;40;34;4;1;4;5;-1-4-31-34-40-;-4-5-;0;1;130000000,00;1;3;420494,80;1;11;26802,70;14;47;1953,80;248;1053;160,60;932;2762;64,70;569;2273;55,20;10589;40123;15,60;13924;53453;13,10;28280;111082;11,70;58565;220823;7,10;225982;855579;5,80;470067;1770373;4,50;0;0;3;2007,40;107;30,10;420;7,80;4781;1,70;6512;3,30;26812;3,80;44917;10,70;103973;2,60;1016392;2,30;SB 624 1399;; +24021;MARDI;12/03/2024;6;11/06/2024;30;38;19;13;46;4;12;-13-19-30-38-46-;-4-12-;0;0;0;0;2;285832,30;1;4;33401,80;3;20;2080,70;135;561;136,60;227;1026;78,90;331;1325;42,90;3246;15548;18,30;5576;25020;12,60;14074;62934;9,30;17316;81999;8,70;79633;363360;6,20;207173;934872;3,80;0;0;1;2227,50;61;19,30;119;10,20;1592;1,90;2543;3,20;8683;4,40;14229;12,50;37621;2,60;329015;2,70;MF 581 4895;; +24020;VENDREDI;08/03/2024;5;07/06/2024;12;11;8;44;16;4;7;-8-11-12-16-44-;-4-7-;0;0;0;1;1;729847,10;1;9;18953,00;14;48;1106,80;275;981;99,70;674;2566;40,30;427;1867;38,90;9394;37704;9,60;11101;44407;9,10;20967;88672;8,50;44036;182988;4,90;148889;615114;4,60;285741;1244791;3,70;0;0;3;701,30;121;12,50;281;5,50;4001;,90;4902;2,10;19029;2,50;26391;8,50;66146;1,90;451452;2,40;FZ 130 0700;; +24019;MARDI;05/03/2024;4;04/06/2024;23;36;2;17;15;8;3;-2-15-17-23-36-;-3-8-;0;0;0;1;2;267433,80;1;7;17858,20;2;34;1145,10;169;746;96,10;369;1869;40,50;334;1509;35,30;5279;26006;10,20;6925;31466;9,40;13510;63100;8,70;26583;129369;5,10;93030;437488;4,80;188773;879843;3,80;1;1131,20;0;0;80;24,80;166;6,70;2278;1,20;3228;2,30;11821;2,90;18679;8,70;42381;2,10;337562;2,40;EK 216 0386;; +24018;VENDREDI;01/03/2024;3;31/05/2024;34;20;4;19;7;2;4;-4-7-19-20-34-;-2-4-;0;0;0;2;4;169602,10;2;9;17617,20;14;58;851,40;251;1001;90,80;600;2268;42,40;442;1890;35,70;7265;30073;11,20;10559;43849;8,50;19886;84586;8,20;31914;138480;6,10;135975;573714;4,60;269177;1169473;3,60;1;1487,40;7;169,90;104;14,00;260;5,60;3109;1,10;4669;2,10;13878;3,30;19838;10,80;61284;1,90;416488;2,50;EQ 807 5438;; +24017;MARDI;27/02/2024;2;28/05/2024;20;3;4;9;12;5;6;-3-4-9-12-20-;-5-6-;0;0;0;1;8;61868,90;2;10;11567,80;8;37;973,80;300;1053;63,00;397;1701;41,20;705;2348;20,90;4147;19655;12,50;10112;37856;7,20;20834;77533;6,60;18895;92139;6,70;108957;449963;4,30;219530;905443;3,40;0;0;4;485,20;134;7,90;205;5,20;1946;1,30;4540;1,50;8706;3,80;13257;11,70;49047;1,70;307624;2,50;DM 640 1485;; +24016;VENDREDI;23/02/2024;1;24/05/2024;27;49;30;24;28;1;12;-24-27-28-30-49-;-1-12-;0;0;0;0;0;0;0;5;155533,60;5;24;1911,90;103;499;169,30;244;1161;76,90;242;1231;51,00;3839;17444;17,90;5147;24559;14,20;14696;66563;9,70;20278;95080;8,30;79326;371425;6,60;234032;1049508;3,80;0;0;2;1224,40;51;26,60;124;10,70;1979;1,70;2510;3,50;10465;4,00;16844;11,60;38641;2,80;337693;2,90;CN 910 7043;; +24015;MARDI;20/02/2024;6;21/05/2024;42;31;37;48;23;7;3;-23-31-37-42-48-;-3-7-;1;1;73448160,00;0;4;143586,80;0;3;44744,80;13;42;995,40;121;520;148,10;402;1974;41,20;204;1091;52,40;6219;29695;9,60;5247;26063;12,20;10614;53216;11,10;36854;167972;4,20;82264;400597;5,60;156876;793739;4,50;0;0;6;352,20;54;21,70;170;6,80;2641;1,10;2346;3,30;15868;2,30;29269;5,80;37719;2,50;389564;2,10;IK 087 1662;; +24014;VENDREDI;16/02/2024;5;17/05/2024;26;14;13;24;8;1;2;-8-13-14-24-26-;-1-2-;0;0;0;0;2;365544,00;5;13;13143,60;14;54;985,50;270;1118;87,60;407;1891;54,80;602;2500;29,10;5065;24074;15,10;11091;45869;8,80;23734;102828;7,30;24629;118061;7,70;139195;592332;4,80;304231;1341440;3,40;0;0;7;406,30;103;15,30;176;8,70;2295;1,70;4861;2,10;11300;4,30;17130;13,30;62363;2,00;430862;2,60;DC 011 7303;; +24013;MARDI;13/02/2024;4;14/05/2024;13;18;46;17;20;9;4;-13-17-18-20-46-;-4-9-;0;0;0;1;2;269795,00;3;6;21018,50;8;34;1155,30;118;550;131,50;365;1598;47,80;289;1261;42,60;5370;24766;10,80;6180;26919;11,10;13179;58929;9,40;28244;131538;5,10;92320;406778;5,20;203255;898873;3,80;1;1183,10;5;189,20;55;21,50;162;7,20;2401;1,20;2786;2,80;12949;2,80;18974;9,00;42472;2,20;328030;2,50;NQ 226 7262;; +24012;VENDREDI;09/02/2024;3;10/05/2024;37;35;45;24;23;12;9;-23-24-35-37-45-;-9-12-;0;0;0;1;1;688624,90;1;4;40235,70;1;19;2638,40;123;600;153,90;250;1209;80,70;285;1310;52,30;3828;18226;18,80;5712;27565;13,80;14427;67644;10,50;21873;101495;8,50;88159;421884;6,40;223124;1039739;4,20;1;1538,50;0;0;64;42,60;135;11,30;2018;1,80;2881;3,50;11624;4,10;19847;11,10;43801;2,80;435600;2,50;SN 756 9757;; +24011;MARDI;06/02/2024;2;07/05/2024;45;7;2;28;21;11;5;-2-7-21-28-45-;-5-11-;0;0;0;0;2;252419,40;3;6;19664,80;6;30;1225,00;160;699;96,80;284;1327;53,90;383;1599;31,40;3664;18003;13,90;6531;29688;9,40;16196;68863;7,50;18179;89461;7,00;86732;388042;5,10;212014;890199;3,60;0;0;4;516,10;70;16,10;142;8,00;1764;1,60;3046;2,50;8995;3,90;14190;11,60;41002;2,20;310401;2,60;HM 944 5631;; +24010;VENDREDI;02/02/2024;1;03/05/2024;13;42;27;23;20;9;5;-13-20-23-27-42-;-5-9-;0;0;0;0;4;171330,20;3;7;22881,50;25;93;536,40;313;1155;79,50;642;2846;34,10;794;2593;26,30;7751;36285;9,40;10817;45275;8,40;24618;96147;7,30;36790;171481;5,00;136195;587629;4,60;291807;1199431;3,60;0;0;12;220,50;152;10,00;287;5,40;3447;1,10;4961;2,10;16884;2,90;24699;9,30;63106;2,00;453117;2,50;VE 765 3773;; +24009;MARDI;30/01/2024;3;30/04/2024;10;27;5;30;19;6;5;-5-10-19-27-30-;-5-6-;0;2;72274808,00;5;11;82195,80;8;22;9605,20;22;83;793,00;580;1933;62,70;725;3057;41,90;1043;3570;25,20;8447;36427;12,30;18664;68776;7,30;34120;129934;7,10;36743;167646;6,70;204081;806112;4,40;402394;1601176;3,50;3;674,00;11;134,80;229;8,70;319;6,10;3659;1,30;8086;1,60;16374;3,80;24236;12,00;88339;1,80;573976;2,50;SR 540 3822;; +24008;VENDREDI;26/01/2024;2;26/04/2024;32;19;41;8;42;12;9;-8-19-32-41-42-;-9-12-;0;0;0;1;9;146288,80;2;8;38463,80;14;51;1879,30;308;1288;137,00;548;2523;73,90;740;3026;43,30;8461;36883;17,70;13251;57510;12,70;33051;140388;9,70;47411;200543;8,20;200262;846899;6,10;513526;2087605;4,00;1;3136,70;7;358,40;136;22,50;269;11,60;4113;1,80;6171;3,30;23135;4,20;40476;11,10;93188;2,70;876812;2,50;RU 715 8901;; +24007;MARDI;23/01/2024;1;23/04/2024;48;23;14;50;39;12;3;-14-23-39-48-50-;-3-12-;0;0;0;0;1;482958,10;1;2;56437,60;6;18;1953,20;76;451;143,60;190;917;74,60;216;1142;42,10;2686;13501;17,80;4110;21242;12,60;9545;48410;10,30;13905;68707;8,80;62263;315496;6,00;144209;725468;4,20;0;0;2;935,50;35;28,80;105;9,80;1386;1,80;2002;3,40;7334;4,40;12780;11,70;30568;2,70;305461;2,40;KR 537 5327;; +24006;VENDREDI;19/01/2024;1;19/04/2024;50;27;28;48;44;7;12;-27-28-44-48-50-;-7-12-;0;1;17000000,00;2;3;215402,70;3;5;30205,90;4;27;1742,20;93;515;168,20;242;1212;75,50;211;1228;52,40;3770;17992;17,80;5163;26591;13,50;11197;59382;11,20;20666;97560;8,20;87291;414068;6,10;192813;952524;4,30;2;691,40;1;1106,30;45;30,70;129;10,60;1901;1,80;2584;3,50;10643;4,00;17880;11,10;42269;2,60;420119;2,30;QE 911 6397;; +24005;MARDI;16/01/2024;7;16/04/2024;18;21;33;45;10;12;8;-10-18-21-33-45-;-8-12-;1;1;87952574,00;1;3;201336,70;3;22;6416,60;3;30;1465,60;194;858;94,40;255;1203;71,10;438;2054;29,20;3444;16758;17,90;6341;30461;11,00;16295;76506;8,10;17551;87790;8,60;84567;410612;5,80;220434;1035293;3,70;0;0;0;0;83;41,20;130;9,40;1641;1,80;2896;2,80;8509;4,40;13738;12,70;39421;2,50;324690;2,70;CU 208 5890;; +24004;VENDREDI;12/01/2024;6;12/04/2024;18;16;17;49;45;9;12;-16-17-18-45-49-;-9-12-;0;0;0;1;3;281236,60;2;10;19718,80;11;41;1498,00;112;674;167,80;220;1434;83,40;261;1682;49,90;4055;22939;18,30;5828;33053;14,10;14593;78911;11,00;21925;125191;8,40;96155;531557;6,20;241543;1282524;4,10;1;1568,60;7;179,20;53;29,50;114;13,70;2029;1,80;2780;3,70;11287;4,30;18145;12,40;46122;2,70;422529;2,60;RT 208 4085;; +24003;MARDI;09/01/2024;5;09/04/2024;12;9;2;39;40;3;1;-2-9-12-39-40-;-1-3-;0;0;0;0;1;563511,30;2;7;18814,50;1;19;2159,00;113;572;132,10;269;1256;63,60;286;1323;42,40;4002;19412;14,40;5995;27088;11,50;13014;60687;9,60;20832;100262;7,00;88648;407111;5,40;194785;904413;3,90;0;0;1;2091,80;57;20,00;106;10,60;1869;1,50;2806;2,70;9520;3,80;15519;10,80;40960;2,30;335397;2,40;LN 958 0675;; +24002;VENDREDI;05/01/2024;4;05/04/2024;18;4;39;7;50;8;3;-4-7-18-39-50-;-3-8-;0;0;0;0;2;359048,50;1;7;23975,80;11;59;886,00;146;749;128,50;395;2001;50,80;319;1713;41,70;6421;32990;10,80;7652;35938;11,10;16974;77956;9,50;34592;166715;5,30;116584;538106;5,20;247530;1133782;4,00;0;0;4;697,50;60;25,00;183;8,40;2919;1,30;3509;2,90;15479;3,10;26097;8,50;53602;2,30;470400;2,30;SA 454 2143;; +24001;MARDI;02/01/2024;3;02/04/2024;7;18;49;46;15;12;10;-7-15-18-46-49-;-10-12-;0;0;0;0;0;0;0;5;123090,60;2;6;6052,50;76;404;165,50;186;822;86,00;260;1142;43,50;2762;12774;19,40;4251;19434;14,20;12122;54361;9,40;14574;67547;9,20;64733;286826;6,80;191135;841787;3,70;0;0;1;1968,40;37;29,50;98;10,90;1361;1,90;2113;3,40;7176;4,70;11833;13,30;31882;2,80;270726;2,90;VM 156 4461;; +23104;VENDREDI;29/12/2023;2;29/03/2024;2;36;19;37;3;9;6;-2-3-19-36-37-;-6-9-;0;0;0;0;3;235723,30;0;5;33055,40;14;38;1354,70;116;622;152,40;327;1684;59,50;236;1301;54,10;5616;27202;12,90;6144;30489;12,80;13203;66465;11,00;32539;150297;5,80;107439;494759;5,60;227954;1068091;4,20;0;0;8;344,70;62;24,70;161;9,50;2675;1,40;2940;3,40;15054;3,10;24451;9,00;50781;2,40;454579;2,40;JK 572 5557;; +23103;MARDI;26/12/2023;1;26/03/2024;47;27;8;30;35;9;10;-8-27-30-35-47-;-9-10-;0;0;0;0;0;0;0;0;0;3;18;32617,80;82;419;143,80;163;727;87,60;179;899;49,70;2303;11216;19,90;4138;19378;12,80;9697;44899;10,30;12833;60460;9,30;64655;294756;6,00;152666;693402;4,10;0;0;0;0;47;59,00;88;11,50;1180;2,10;1949;3,40;6569;4,80;10761;13,60;31148;2,60;291732;2,50;RK 918 9326;; +23102;VENDREDI;22/12/2023;4;22/03/2024;14;49;34;44;6;12;4;-6-14-34-44-49-;-4-12-;0;1;55662868,00;2;7;113308,60;1;5;37074,90;8;54;1069,20;136;698;152,30;259;1546;72,70;369;1739;45,40;4028;20788;19,00;6953;33434;13,10;16608;78958;10,30;22785;112843;8,80;100888;475534;6,50;255423;1187664;4,20;0;0;4;779,90;61;27,50;137;12,30;2078;2,00;3419;3,30;11841;4,50;20062;12,40;49508;2,80;473364;2,60;MS 622 2438;; +23101;MARDI;19/12/2023;3;19/03/2024;20;43;41;45;10;12;2;-10-20-41-43-45-;-2-12-;0;0;0;1;1;529973,70;0;7;17694,70;3;13;2967,70;79;396;179,40;187;915;82,10;216;1068;49,40;2868;13514;19,50;4116;19855;14,80;10261;48406;11,30;16015;73827;8,90;66465;308486;6,70;165057;763848;4,40;1;1175,50;2;470,20;44;26,70;95;11,90;1435;2,00;2022;3,80;8269;4,40;14192;11,90;32757;2,90;332498;2,50;TN 553 0599;; +23100;VENDREDI;15/12/2023;2;15/03/2024;2;13;37;38;48;9;5;-2-13-37-38-48-;-5-9-;0;0;0;0;1;695067,70;1;5;32489,70;7;35;1445,60;151;689;135,20;334;1751;56,20;294;1327;52,10;5653;27167;12,70;6437;29721;12,90;14846;64665;11,10;34064;158926;5,40;106650;470477;5,80;234048;991215;4,40;0;0;4;598,30;75;22,10;158;10,40;2700;1,50;3215;3,40;15732;3,20;28362;8,40;51466;2,60;502254;2,30;CL 667 1862;; +23099;MARDI;12/12/2023;1;12/03/2024;43;39;28;37;6;9;12;-6-28-37-39-43-;-9-12-;0;0;0;0;1;523847,30;1;2;61215,80;4;21;1815,90;100;444;158,20;193;813;91,30;280;1099;47,40;2781;12674;20,50;4546;20231;14,30;12449;50775;10,60;16384;70525;9,30;71372;304485;6,70;185652;760714;4,30;0;0;1;2357,10;45;29,10;103;12,70;1500;2,10;2291;3,70;8636;4,70;15011;12,50;35979;2,90;364465;2,50;DE 599 0522;; +23098;VENDREDI;08/12/2023;3;08/03/2024;48;30;50;42;17;4;8;-17-30-42-48-50-;-4-8-;0;1;240000000,00;1;5;1584039,10;3;16;24836,40;29;89;1390,70;433;1496;152,40;1147;3751;64,20;1067;3496;48,40;18568;59915;14,10;22280;72556;13,00;49811;163113;10,70;107577;342084;6,20;341513;1100646;6,00;773981;2482059;4,30;1;5260,50;13;323,70;221;23,80;547;9,40;8195;1,50;10175;3,40;47320;3,40;83825;9,00;154150;2,70;1557384;2,40;FK 500 2787;; +23097;MARDI;05/12/2023;2;05/03/2024;6;20;4;24;25;5;9;-4-6-20-24-25-;-5-9-;0;0;0;3;8;146494,60;6;16;17119,10;22;88;969,40;447;1517;103,50;994;3609;46,00;996;3338;34,90;13532;50374;11,50;18586;64967;10,00;41053;139986;8,60;67769;260163;5,60;252088;906029;5,10;554735;1940512;3,80;1;3054,30;10;244,30;196;15,30;410;7,30;5799;1,30;8096;2,40;29470;3,20;46094;9,50;111451;2,20;866454;2,50;NZ 837 2820;; +23096;VENDREDI;01/12/2023;1;01/03/2024;38;10;4;14;50;9;12;-4-10-14-38-50-;-9-12-;0;0;0;2;6;266632,90;6;18;20772,10;14;58;2007,90;392;1609;133,30;757;3072;73,80;952;3942;40,40;11761;45380;17,50;18562;71488;12,40;45754;174658;9,40;63049;241471;8,30;280126;1055902;5,90;693640;2566667;3,90;2;2075,10;7;474,30;175;23,10;347;11,80;5441;1,80;8615;3,10;29536;4,30;49376;12,00;127619;2,60;1137532;2,60;EI 943 3492;; +23095;MARDI;28/11/2023;7;27/02/2024;27;44;12;33;16;7;8;-12-16-27-33-44-;-7-8-;0;1;83068817,00;2;4;145719,30;0;6;22704,60;13;45;942,90;182;846;92,30;334;1500;55,00;337;1675;34,60;4323;20184;14,30;8034;36297;8,90;14635;67580;8,90;22513;103722;7,00;110729;494025;4,60;199936;940100;3,90;2;598,10;4;239,20;67;17,50;148;8,00;1911;1,50;3422;2,30;10242;3,60;15982;10,70;49303;1,90;374723;2,20;FA 161 9226;; +23094;VENDREDI;24/11/2023;6;23/02/2024;48;15;29;20;39;7;1;-15-20-29-39-48-;-1-7-;0;0;0;0;3;243253,90;0;8;21319,60;10;39;1362,10;137;704;139,00;329;1584;65,30;299;1635;44,40;5610;24643;14,70;6393;31928;12,60;13974;72282;10,40;32448;137550;6,60;102570;488145;5,80;214738;1078084;4,30;0;0;5;528,80;67;21,60;158;9,20;2507;1,40;2897;3,30;14480;3,10;25888;8,20;46058;2,60;462114;2,20;OI 764 7249;; +23093;MARDI;21/11/2023;5;20/02/2024;46;47;29;34;19;2;3;-19-29-34-46-47-;-2-3-;0;0;0;1;3;175300,00;1;5;24582,30;6;25;1531,30;74;436;161,70;192;1001;74,40;173;970;54,00;3067;14690;17,80;4284;21592;13,50;9000;45902;11,80;18601;90678;7,20;74806;352623;5,80;146621;721492;4,60;0;0;2;974,50;34;31,80;83;12,50;1405;1,90;1966;3,60;8678;3,80;16094;9,70;34177;2,50;362926;2,10;AG 692 9478;; +23092;VENDREDI;17/11/2023;4;16/02/2024;26;50;24;2;46;2;7;-2-24-26-46-50-;-2-7-;0;0;0;1;4;171009,90;0;2;79935,60;9;33;1508,90;117;646;141,90;424;1857;52,20;258;1279;53,20;7111;29115;11,70;6288;30178;12,50;12507;61288;11,50;42587;167340;5,10;103711;473573;5,70;194794;942535;4,60;0;0;5;525,10;59;24,70;205;7,00;3181;1,10;2976;3,20;18799;2,40;32000;6,60;47871;2,40;481061;2,10;RY 968 0146;; +23091;MARDI;14/11/2023;3;13/02/2024;13;50;36;16;44;3;5;-13-16-36-44-50-;-3-5-;0;0;0;0;1;486731,50;2;4;28439,20;8;30;1181,00;80;477;136,80;245;1201;57,40;188;971;49,90;3883;20003;12,10;4298;21924;12,30;8937;45060;11,10;23907;115461;5,20;68980;348491;5,50;142164;693633;4,40;0;0;3;617,90;27;36,70;118;8,60;1802;1,40;2094;3,20;10663;3,00;19176;7,70;32562;2,50;329596;2,20;IZ 946 0638;; +23090;VENDREDI;10/11/2023;2;09/02/2024;30;42;10;21;38;12;2;-10-21-30-38-42-;-2-12-;0;0;0;0;2;324751,70;2;10;15179,90;5;26;1818,50;166;716;121,60;269;1261;73,00;437;1800;35,90;3611;17183;18,80;7043;30440;11,80;17708;75435;8,90;19464;91145;8,90;95376;431046;5,90;241855;1075258;3,80;0;0;1;2556,20;72;19,70;136;10,40;1833;1,90;3351;2,80;9906;4,40;16473;12,40;45407;2,50;384492;2,60;KA 106 4774;; +23089;MARDI;07/11/2023;1;06/02/2024;8;10;11;39;30;10;4;-8-10-11-30-39-;-4-10-;0;0;0;0;0;0;1;3;189164,10;8;29;1154,60;107;536;115,00;220;1001;65,10;296;1340;34,10;3427;14771;15,50;5409;23231;11,00;13007;57012;8,30;17418;76374;7,50;74500;331651;5,40;181616;808515;3,60;0;0;2;894,10;53;18,30;114;8,60;1642;1,40;2564;2,50;8444;3,60;12694;11,30;35352;2,30;258784;2,70;ST 445 2708;; +23088;VENDREDI;03/11/2023;2;02/02/2024;31;47;21;39;8;9;5;-8-21-31-39-47-;-5-9-;0;1;31218086,00;0;6;123697,30;1;4;43365,10;6;53;1019,40;153;823;120,90;343;2208;47,60;275;1628;45,40;5158;31335;11,70;6463;35295;11,60;13710;74644;10,20;29376;170549;5,40;95499;522472;5,60;204137;1093856;4,30;0;0;1;2524,00;77;17,50;132;10,60;2266;1,50;2976;3,10;12908;3,30;23084;8,80;44336;2,50;427975;2,30;IF 499 9158;; +23087;MARDI;31/10/2023;1;30/01/2024;20;40;5;50;7;10;2;-5-7-20-40-50-;-2-10-;0;0;0;0;5;96734,80;0;3;37680,80;4;21;1676,60;106;577;112,40;270;1184;57,90;287;1402;34,30;3625;15789;15,20;5342;25925;10,30;12430;59923;8,30;18562;82503;7,30;73964;339907;5,60;174136;805649;3,80;0;0;2;951,50;45;23,40;127;8,20;1808;1,40;2555;2,70;9283;3,50;14670;10,40;35744;2,40;294519;2,50;OI 825 1787;; +23086;VENDREDI;27/10/2023;1;26/01/2024;29;48;33;49;35;8;3;-29-33-35-48-49-;-3-8-;0;1;17000000,00;0;2;322693,90;1;2;75418,80;5;25;1879,20;110;524;165,10;291;1391;65,70;205;1116;57,60;4795;23256;13,80;5161;24878;14,40;11004;53753;12,40;29068;139631;5,70;85036;405387;6,20;180060;850884;4,80;0;0;2;1302,90;52;27,30;137;10,40;2221;1,60;2566;3,70;13294;3,40;26125;8,00;40730;2,90;475913;2,10;MY 351 7471;; +23085;MARDI;24/10/2023;2;23/01/2024;18;16;8;31;34;6;9;-8-16-18-31-34-;-6-9-;0;1;26274769,00;0;1;484142,90;1;7;16164,50;10;41;859,60;141;608;106,70;331;1425;48,10;311;1235;39,00;4141;19526;12,30;5597;25009;10,70;12012;54674;9,10;22192;103331;5,80;79806;354127;5,30;171211;774426;3,90;0;0;5;385,00;68;15,70;154;6,80;1919;1,30;2593;2,70;10257;3,20;17165;9,00;36833;2,30;320028;2,40;GB 497 3419;; +23084;VENDREDI;20/10/2023;1;19/01/2024;20;40;28;2;45;5;1;-2-20-28-40-45-;-1-5-;0;0;0;0;0;0;2;7;114759,50;6;33;1436,30;136;613;142,40;343;1377;67,00;334;1379;47,00;5092;21173;15,30;6710;29067;12,40;15821;64755;10,40;28195;116541;7,00;105018;436996;5,80;237660;960647;4,30;0;0;1;2888,90;63;25,40;173;9,20;2338;1,70;3233;3,20;12834;3,90;22590;10,20;49709;2,60;469685;2,40;VU 044 6875;; +23083;MARDI;17/10/2023;5;16/01/2024;40;20;10;35;17;4;3;-10-17-20-35-40-;-3-4-;0;1;67318140,00;2;4;139123,90;4;12;10838,50;3;26;1558,10;149;724;103,00;326;1458;54,10;339;1523;36,30;3944;18681;14,80;7321;32752;9,40;14665;68098;8,40;20528;95136;7,30;96367;428190;5,10;192676;866503;4,00;1;1233,80;2;493,50;66;18,40;155;7,90;1850;1,60;3279;2,50;9448;4,00;15130;11,70;43909;2,20;380582;2,30;JX 489 7822;; +23082;VENDREDI;13/10/2023;4;12/01/2024;41;26;21;40;28;4;2;-21-26-28-40-41-;-2-4-;0;0;0;0;1;867786,70;3;12;16901,30;6;32;1974,10;222;748;155,50;505;1813;67,80;444;1621;53,30;8270;28571;15,10;10767;37127;12,90;22998;80354;11,10;47829;158565;6,80;175811;590399;5,80;375500;1274707;4,30;0;0;2;2403,20;114;23,40;250;10,40;3886;1,70;5250;3,30;22132;3,70;38753;9,90;83565;2,60;806745;2,30;KA 345 1524;; +23081;MARDI;10/10/2023;3;09/01/2024;18;33;20;22;43;3;9;-18-20-22-33-43-;-3-9-;0;0;0;0;1;515171,20;0;6;20067,30;7;37;1013,50;150;574;120,30;333;1564;46,60;262;1231;41,60;5169;23158;11,00;5773;25995;11,00;12772;55643;9,50;28075;122739;5,20;84520;381729;5,30;178217;788883;4,10;0;0;1;2109,40;61;19,20;155;7,40;2371;1,20;2628;2,90;13089;2,80;21547;7,80;39751;2,40;354369;2,30;JP 503 1005;; +23080;VENDREDI;06/10/2023;2;05/01/2024;31;43;21;29;34;2;9;-21-29-31-34-43-;-2-9-;0;0;0;0;1;687153,20;1;5;32119,80;9;39;1282,60;155;623;147,90;364;1607;60,60;313;1347;50,80;5752;24918;13,70;7196;30408;12,50;15384;66296;10,70;31964;136495;6,30;109181;465860;5,80;236705;1002138;4,30;0;0;6;504,20;75;21,50;160;10,40;2727;1,50;3457;3,20;14875;3,50;26176;9,30;52455;2,60;520209;2,30;QZ 466 3294;; +23079;MARDI;03/10/2023;1;02/01/2024;45;6;22;24;20;5;4;-6-20-22-24-45-;-4-5-;0;0;0;2;4;124708,10;1;8;14573,10;2;24;1513,00;113;509;131,40;282;1258;56,20;241;1067;46,50;3959;17205;14,40;6018;25810;10,70;12465;52762;9,70;20428;90431;6,90;88331;379813;5,10;182764;770818;4,10;0;0;2;1058,60;56;20,60;130;9,00;1838;1,50;2856;2,70;9697;3,70;14962;11,30;41866;2,20;343321;2,40;MV 249 2574;; +23078;VENDREDI;29/09/2023;4;29/12/2023;32;9;11;21;13;7;2;-9-11-13-21-32-;-2-7-;1;1;130000000,00;3;16;81127,90;1;25;12135,00;42;147;642,80;531;2294;75,80;1657;5675;32,40;1027;4422;29,20;20437;69584;9,20;24393;90926;7,90;46428;177627;7,50;92300;321029;5,00;306346;1144130;4,40;590325;2286033;3,60;2;1621,00;17;152,50;236;13,70;705;4,50;8824;,90;10676;2,00;40598;2,40;58302;8,00;136596;1,90;938343;2,40;TT 262 4377;; +23077;MARDI;26/09/2023;3;26/12/2023;14;6;23;2;19;5;7;-2-6-14-19-23-;-5-7-;0;0;0;2;6;87504,60;4;14;8764,80;24;75;509,60;274;1010;69,70;692;2645;28,10;518;1992;26,20;7867;31767;8,20;9600;38632;7,50;17664;72530;7,40;34460;146389;4,40;113319;480181;4,30;210806;918463;3,60;1;1158,80;7;132,40;136;8,40;309;3,60;3303;,80;4337;1,70;15164;2,30;21784;7,60;51691;1,80;345657;2,40;OB 498 5779;; +23076;VENDREDI;22/09/2023;2;22/12/2023;3;23;35;34;24;8;5;-3-23-24-34-35-;-5-8-;0;0;0;0;1;681329,00;1;5;31847,50;8;35;1417,10;113;643;142,00;391;2025;47,60;279;1346;50,40;7247;33739;10,00;6577;31719;11,90;14610;67383;10,40;40678;178760;4,70;111667;507911;5,20;248053;1067407;4,00;0;0;2;1451,10;57;28,20;161;9,90;3185;1,20;3162;3,30;17983;2,80;28983;8,00;52367;2,50;469595;2,40;FX 396 8800;; +23075;MARDI;19/09/2023;1;19/12/2023;10;31;41;15;42;2;5;-10-15-31-41-42-;-2-5-;0;0;0;1;1;473970,00;2;2;55387,30;2;22;1568,30;67;433;146,70;249;1158;58,00;164;883;53,40;3968;18272;12,90;4182;21182;12,40;8994;42564;11,50;22800;103564;5,70;75833;338329;5,50;148736;674676;4,40;0;0;1;1896,60;30;35,10;100;10,40;1782;1,40;1977;3,50;9976;3,20;17095;8,90;35294;2,40;321104;2,30;TL 095 8812;; +23074;VENDREDI;15/09/2023;4;15/12/2023;21;45;12;48;14;8;11;-12-14-21-45-48-;-8-11-;0;2;27475869,00;1;3;264520,20;1;8;23183,50;8;26;2221,80;147;728;146,10;319;1599;70,30;367;1650;47,90;4471;23985;16,40;7299;35296;12,40;18362;83822;9,70;24131;128333;7,70;110254;537055;5,80;274115;1272189;3,90;1;1624,40;3;433,10;67;23,20;173;9,30;2247;1,70;3368;3,20;11890;4,20;20123;11,60;52160;2,50;444605;2,60;SW 459 1895;; +23073;MARDI;12/09/2023;3;12/12/2023;14;5;42;36;40;11;2;-5-14-36-40-42-;-2-11-;0;0;0;1;2;258198,50;1;3;40230,10;5;21;1790,10;94;440;157,30;217;1025;71,40;179;999;51,40;3380;15637;16,40;4209;20855;13,70;9922;48569;10,90;18612;86644;7,40;67524;322572;6,30;159781;751416;4,30;0;0;1;2051,50;34;33,50;103;11,00;1681;1,60;2051;3,60;9213;3,80;15901;10,30;32674;2,80;322540;2,50;HF 956 4344;; +23072;VENDREDI;08/09/2023;2;08/12/2023;33;20;10;21;26;4;3;-10-20-21-26-33-;-3-4-;0;0;0;1;4;167780,70;2;5;31370,40;11;36;1357,10;174;779;115,50;375;1688;56,30;360;1548;43,10;5200;23538;14,20;9432;39063;9,50;18393;77154;8,90;26263;117661;7,10;132570;555775;4,70;260748;1102069;3,80;1;1562,70;3;416,70;68;22,90;167;9,30;2344;1,60;4220;2,40;11918;4,00;18573;12,10;60387;2,10;464471;2,40;SE 101 9319;; +23071;MARDI;05/09/2023;1;05/12/2023;41;48;7;1;24;10;12;-1-7-24-41-48-;-10-12-;0;0;0;0;3;162494,70;2;9;12659,20;4;21;1689,80;86;370;176,60;163;809;85,40;211;1057;45,90;2542;12234;19,80;3984;18242;14,80;12057;53554;9,40;13823;65178;9,30;60488;275963;6,90;186614;825123;3,70;0;0;3;660,50;48;22,40;75;14,60;1307;2,00;2044;3,50;7156;4,70;11782;13,40;30882;2,90;271695;2,90;IF 603 7505;; +23070;VENDREDI;01/09/2023;9;01/12/2023;35;43;37;5;4;5;6;-4-5-35-37-43-;-5-6-;1;1;109268140,00;2;2;470297,30;0;7;31404,60;4;34;2013,80;162;729;173,00;379;1724;77,30;346;1625;57,60;6711;29522;15,80;9262;39276;13,30;18964;80385;12,10;39496;167390;7,00;158392;640740;5,70;318837;1302539;4,50;1;2297,50;3;612,60;76;30,20;191;11,80;3139;1,80;4415;3,40;18296;3,90;29528;11,20;72927;2,50;715488;2,30;RZ 000 8026;; +23069;MARDI;29/08/2023;8;28/11/2023;21;40;23;32;49;11;8;-21-23-32-40-49-;-8-11-;0;0;0;1;1;636120,60;2;6;24778,60;7;37;1251,50;144;618;138,00;300;1349;66,80;299;1398;45,30;4505;19845;15,90;6369;28320;12,40;14859;65585;10,00;24367;104102;7,60;98029;422460;5,90;230361;965572;4,10;0;0;5;554,50;62;24,40;140;10,90;2097;1,80;2952;3,40;11456;4,10;19850;11,10;44981;2,70;438072;2,50;GM 852 9393;; +23068;VENDREDI;25/08/2023;7;24/11/2023;44;35;16;34;17;5;10;-16-17-34-35-44-;-5-10-;0;0;0;0;3;262115,90;2;6;30630,40;6;37;1547,10;157;756;139,40;411;1628;68,40;348;1635;47,90;5596;25593;15,30;7756;35610;12,20;17728;79034;10,20;32120;138950;7,00;120470;526240;5,80;278575;1197301;4,10;0;0;2;1102,90;86;21,30;196;9,30;2656;1,70;3616;3,30;15149;3,70;25904;10,20;55587;2,70;529845;2,50;WK 502 1672;; +23067;MARDI;22/08/2023;6;21/11/2023;23;9;40;2;32;6;7;-2-9-23-32-40-;-6-7-;0;0;0;0;7;82102,90;0;5;26864,30;8;35;1195,30;162;808;95,30;319;1472;55,30;303;1625;35,20;4613;20111;14,20;7447;31518;10,10;13792;64403;9,20;25720;109273;6,50;107178;447873;5,00;195747;885697;4,10;0;0;5;468,00;71;18,30;138;9,20;2076;1,50;3389;2,50;11937;3,30;19952;9,40;49192;2,10;430782;2,10;JT 636 1851;; +23066;VENDREDI;18/08/2023;5;17/11/2023;47;25;23;30;44;9;12;-23-25-30-44-47-;-9-12-;0;0;0;1;5;141503,90;0;4;41339,70;4;34;1514,80;108;644;147,30;213;1238;81,00;288;1459;48,30;3326;18387;19,10;5532;29658;13,20;14010;72652;10,00;17959;98111;9,00;86518;453505;6,10;225743;1138029;3,90;1;1402,90;2;561,10;61;22,20;102;13,60;1709;2,00;2661;3,50;8977;4,80;15143;13,30;41054;2,70;381205;2,60;HZ 033 3032;; +23065;MARDI;15/08/2023;4;14/11/2023;3;32;5;43;9;6;10;-3-5-9-32-43-;-6-10-;0;0;0;1;4;122572,40;0;2;57294,40;1;17;2099,50;91;448;146,70;225;1109;62,60;219;1137;42,90;3211;15560;15,60;4695;22553;12,00;11832;58173;8,70;16558;81329;7,50;71439;333639;5,70;174767;843678;3,60;0;0;0;0;41;68,00;111;8,90;1601;1,50;2209;2,90;7940;3,90;13336;10,70;33851;2,40;278089;2,50;AF 362 0161;; +23064;VENDREDI;11/08/2023;3;10/11/2023;10;13;34;4;6;3;5;-4-6-10-13-34-;-3-5-;0;0;0;1;3;219719,20;1;7;22007,90;19;59;813,20;256;887;99,60;580;2513;37,10;501;1871;35,00;7471;33401;9,80;9921;41304;8,80;20441;82076;8,30;35800;168411;4,90;125269;561320;4,60;253766;1104392;3,70;0;0;7;355,60;121;11,30;268;5,10;3221;1,00;4520;2,00;15665;2,70;22829;8,70;56177;2,00;403868;2,40;SR 455 8629;; +23063;MARDI;08/08/2023;2;07/11/2023;40;8;33;9;35;6;3;-8-9-33-35-40-;-3-6-;0;0;0;0;2;245845,50;0;2;57458,10;3;23;1556,20;89;440;149,80;231;1141;61,00;164;987;49,60;4352;19202;12,70;4079;21226;12,80;8822;45557;11,10;25797;109652;5,60;71646;349370;5,50;147153;728739;4,20;0;0;1;1883,70;49;21,30;106;9,70;2030;1,20;1942;3,50;12068;2,70;19855;7,60;33960;2,50;322078;2,30;ML 851 3453;; +23062;VENDREDI;04/08/2023;1;03/11/2023;15;31;48;20;44;1;3;-15-20-31-44-48-;-1-3-;0;0;0;1;4;159371,20;0;9;16554,50;8;28;1657,30;117;636;134,40;231;1303;69,30;228;1369;46,30;4165;19841;16,00;5593;27388;12,90;12004;61041;10,80;23821;112585;7,00;88883;418775;6,00;191614;921973;4,30;0;1357,80;3;362,00;52;26,10;97;13,70;1934;1,70;2633;3,40;10970;3,80;18775;10,40;41810;2,60;414545;2,30;UQ 314 6381;; +23061;MARDI;01/08/2023;6;31/10/2023;35;27;25;45;40;7;11;-25-27-35-40-45-;-7-11-;0;1;72144196,00;0;2;293061,60;0;3;45662,20;8;51;836,60;104;597;131,60;263;1279;64,90;247;1216;48,00;3764;18880;15,40;5044;25530;12,70;11204;58000;10,40;23143;109013;6,70;79512;380992;6,00;169674;824731;4,50;0;0;3;759,80;54;22,60;125;9,80;1767;1,70;2445;3,40;11051;3,50;19346;9,40;37277;2,70;398960;2,20;ID 607 7223;; +23060;VENDREDI;28/07/2023;5;27/10/2023;38;7;34;32;33;9;6;-7-32-33-34-38-;-6-9-;0;0;0;0;0;0;2;8;110849,10;5;25;2093,00;113;604;159,50;263;1588;64,10;245;1452;49,30;4976;26395;13,50;5611;30296;13,10;12260;66535;11,10;29963;150228;5,90;94594;483808;5,80;205087;1060382;4,30;0;0;2;1306,70;62;23,00;118;12,20;2233;1,60;2728;3,50;13366;3,30;23974;8,70;43733;2,70;440281;2,30;UP 999 1591;; +23059;MARDI;25/07/2023;4;24/10/2023;11;7;27;5;37;12;6;-5-7-11-27-37-;-6-12-;0;0;0;0;5;104441,70;2;15;8136,50;7;24;1583,90;164;682;102,60;331;1291;57,30;526;2353;22,10;3666;16128;16,10;6712;30007;9,60;21024;93361;5,70;17060;79211;8,20;82599;378064;5,40;235301;1066813;3,10;0;0;4;477,30;84;12,40;175;5,90;1840;1,40;3195;2,20;8364;3,90;13088;11,70;38253;2,20;269384;2,80;KO 197 6486;; +23058;VENDREDI;21/07/2023;3;20/10/2023;7;35;33;36;31;10;7;-7-31-33-35-36-;-7-10-;0;0;0;0;4;164674,30;0;4;38487,10;7;23;2084,80;90;593;148,90;270;1378;67,70;235;1297;50,50;4736;21089;15,50;5318;27088;13,50;11277;58941;11,50;28187;118614;6,90;93794;449586;5,70;189118;947756;4,40;0;0;3;823,30;38;35,10;132;10,20;2294;1,40;2536;3,50;13528;3,10;21588;9,20;43686;2,50;421083;2,30;PL 062 9336;; +23057;MARDI;18/07/2023;2;17/10/2023;34;35;12;4;45;9;1;-4-12-34-35-45-;-1-9-;0;0;0;1;2;243239,50;2;6;18949,60;4;26;1362,00;86;406;160,60;228;1059;65,10;189;1005;48,20;3497;15657;15,40;4063;19519;13,80;9903;46764;10,70;19447;87899;6,90;64128;303565;6,30;156179;723488;4,20;0;0;1;1860,40;49;20,60;101;10,10;1591;1,60;1911;3,50;8848;3,60;14970;9,90;29982;2,80;293873;2,50;HM 567 0342;; +23056;VENDREDI;14/07/2023;1;13/10/2023;5;29;48;8;35;5;6;-5-8-29-35-48-;-5-6-;0;0;0;0;2;309646,00;0;6;24123,10;4;28;1610,00;140;651;127,50;303;1413;62,10;284;1350;45,60;4398;21537;14,30;6454;30850;11,10;12441;60425;10,60;22136;113533;6,80;100813;470633;5,10;194073;919464;4,20;0;0;3;762,00;59;20,80;138;9,10;1958;1,60;2869;2,90;10093;3,90;15752;11,60;45332;2,20;387716;2,30;GZ 094 7622;; +23055;MARDI;11/07/2023;6;10/10/2023;48;28;2;11;42;4;9;-2-11-28-42-48-;-4-9-;0;2;36621161,00;0;3;195184,20;2;6;22808,80;7;43;991,30;117;568;138,20;338;1662;49,90;304;1298;44,90;5333;24767;11,70;6242;27258;11,90;14437;61942;9,70;28539;131874;5,50;92712;407310;5,60;211571;916501;4,00;0;0;5;455,40;50;25,30;161;7,70;2321;1,30;2885;2,90;12472;3,10;20663;8,80;41634;2,40;357770;2,50;WK 386 6791;; +23054;VENDREDI;07/07/2023;5;06/10/2023;2;12;19;11;38;8;4;-2-11-12-19-38-;-4-8-;0;0;0;0;1;731833,60;1;6;28506,90;16;50;1065,50;242;926;105,90;589;2466;42,00;513;2115;34,40;7661;33617;10,80;10402;42976;9,40;24046;95953;7,80;35380;165314;5,50;133944;586538;4,90;297076;1288917;3,60;0;0;5;542,00;101;14,90;228;6,50;3262;1,10;4568;2,10;15118;3,10;22279;9,70;59807;2,00;406226;2,60;FL 752 1530;; +23053;MARDI;04/07/2023;4;03/10/2023;23;36;44;17;14;8;2;-14-17-23-36-44-;-2-8-;0;0;0;0;2;272715,70;1;10;12747,60;11;58;684,50;157;719;101,70;525;2739;28,20;321;1611;33,70;6324;30195;8,90;6475;31605;9,50;13850;66346;8,50;27941;132533;5,10;87308;421101;5,10;182299;891791;3,80;0;0;5;399,00;84;12,80;238;4,60;2817;,90;2998;2,40;12544;2,70;20221;7,90;40047;2,20;321238;2,40;EP 606 9951;; +23052;VENDREDI;30/06/2023;3;29/09/2023;29;25;6;36;1;10;7;-1-6-25-29-36-;-7-10-;0;0;0;0;3;225928,10;2;6;26401,50;10;31;1591,60;126;646;140,60;360;1428;67,20;301;1474;45,80;4895;21588;15,60;6633;31013;12,10;14684;70921;9,80;26706;116296;7,30;102432;470703;5,60;222753;1064851;4,00;0;0;6;410,90;62;22,00;170;8,00;2424;1,30;3057;2,90;12795;3,30;21783;9,00;47527;2,30;416642;2,30;DF 321 7979;; +23051;MARDI;27/06/2023;2;26/09/2023;27;22;44;17;23;7;1;-17-22-23-27-44-;-1-7-;0;0;0;1;2;244467,60;1;6;19045,30;7;33;1078,50;170;763;85,90;380;1602;43,20;411;1648;29,50;5309;22649;10,70;6928;31074;8,70;14783;68314;7,40;24861;103274;5,90;91360;417505;4,60;185265;882869;3,50;0;0;6;301,20;67;14,90;152;6,50;2366;1,00;3023;2,20;11026;2,80;16028;9,00;40672;2,00;288500;2,50;VU 548 9313;; +23050;VENDREDI;23/06/2023;1;22/09/2023;11;3;33;46;1;10;11;-1-3-11-33-46-;-10-11-;0;0;0;0;2;318086,90;1;1;148684,30;4;23;2013,50;125;622;137,10;218;1242;72,60;381;1683;37,60;3443;17629;17,90;5906;28315;12,40;16964;76547;8,50;17967;91328;8,70;84867;400175;6,20;244664;1100172;3,60;0;0;3;801,80;72;18,50;122;10,80;1762;1,80;2929;3,00;9317;4,40;14404;13,30;41606;2,60;344057;2,70;NT 848 3077;; +23049;MARDI;20/06/2023;5;19/09/2023;35;28;11;32;17;6;5;-11-17-28-32-35-;-5-6-;0;1;64343648,00;2;5;111169,00;0;3;43303,30;8;28;1445,10;176;808;92,20;271;1427;55,20;342;1595;34,70;3971;19914;13,90;6738;32401;9,50;13408;64230;8,90;20330;101300;6,80;95287;445356;4,90;187634;892025;3,90;2;568,00;3;302,90;72;15,50;130;8,60;1677;1,60;2990;2,50;8979;3,90;14225;11,50;41840;2,20;343298;2,30;QQ 390 9739;; +23048;VENDREDI;16/06/2023;4;15/09/2023;34;7;41;26;44;9;11;-7-26-34-41-44-;-9-11-;0;0;0;0;2;357495,20;0;0;0;7;30;7305,10;128;725;132,20;281;1422;71,20;348;1660;42,90;3954;20597;17,20;6485;32355;12,20;15262;73053;10,10;21781;112220;7,90;96833;474646;5,90;232780;1090118;4,10;0;0;6;449,00;59;25,30;129;11,40;1873;1,90;3039;3,20;10438;4,40;17743;12,10;45415;2,60;422301;2,50;WK 325 7642;; +23047;MARDI;13/06/2023;3;12/09/2023;15;16;21;50;41;5;6;-15-16-21-41-50-;-5-6-;0;0;0;1;2;260975,40;1;4;30497,10;5;24;1583,10;87;431;162,30;224;1137;65,00;185;1003;51,80;3361;17324;15,00;4466;23096;12,50;9405;47792;11,20;19514;94572;6,90;76278;370621;5,50;154840;754062;4,30;0;0;2;1007,30;43;25,40;93;12,00;1573;1,70;2078;3,50;8609;4,00;15493;10,40;34597;2,60;354390;2,20;KC 639 8760;; +23046;VENDREDI;09/06/2023;2;08/09/2023;18;26;36;50;41;11;12;-18-26-36-41-50-;-11-12-;0;0;0;0;0;0;1;6;139523,40;5;27;1829,40;89;491;185,30;195;1106;86,90;244;1246;54,20;3298;16897;20,00;4697;24280;15,50;12531;61820;11,30;17569;90562;9,30;73935;368537;7,20;206946;984666;4,30;0;0;1;2649,60;55;26,70;107;13,50;1727;2,10;2397;4,00;9061;5,00;14886;14,20;36908;3,20;378124;2,70;RA 701 3921;; +23045;MARDI;06/06/2023;1;05/09/2023;21;13;39;32;50;10;2;-13-21-32-39-50-;-2-10-;0;0;0;0;2;248559,50;3;10;11618,40;2;13;2783,70;96;483;138,00;214;1069;65,90;208;1277;38,70;3209;15267;16,20;4241;21545;12,80;10318;53210;9,60;18391;81443;7,60;65948;323130;6,00;158557;788725;4,00;0;0;2;970,90;46;22,90;105;10,20;1543;1,70;2085;3,40;8978;3,70;15883;9,80;31769;2,70;314048;2,40;RL 043 9910;; +23044;VENDREDI;02/06/2023;8;01/09/2023;25;3;15;43;12;10;11;-3-12-15-25-43-;-10-11-;0;1;130000000,00;1;7;172851,10;4;19;14883,50;10;43;2048,40;375;1432;113,30;534;2355;72,80;1064;4021;29,90;7365;33967;17,70;14267;58189;11,50;41077;163451;7,60;38103;176285;8,50;191350;805125;5,90;539981;2190204;3,50;0;0;3;1633,90;142;18,90;264;10,10;3546;1,80;6445;2,70;18236;4,60;29932;13,00;88493;2,50;712442;2,70;NG 971 7608;; +23043;MARDI;30/05/2023;7;29/08/2023;27;47;33;29;32;8;2;-27-29-32-33-47-;-2-8-;0;0;0;2;5;117606,70;0;5;27486,60;7;25;1712,20;102;504;156,40;248;1423;58,50;204;1059;55,30;4618;23583;12,40;4641;24707;13,20;10460;55123;11,00;28909;137171;5,30;76638;390205;5,90;170177;869044;4,30;1;1162,80;4;232,50;52;22,30;117;9,80;2029;1,40;2071;3,70;12635;2,80;22682;7,40;33987;2,70;351438;2,30;KW 464 0380;; +23042;VENDREDI;26/05/2023;6;25/08/2023;38;25;15;37;41;1;7;-15-25-37-38-41-;-1-7-;0;0;0;0;4;190665,00;0;3;59415,40;6;31;1790,90;111;733;139,50;297;1525;70,80;270;1524;49,80;5182;24475;15,50;6398;33279;12,70;13016;72955;10,80;32656;142592;6,70;98254;499263;6,00;208254;1098037;4,40;0;0;3;901,30;58;25,40;158;9,20;2313;1,60;2924;3,30;14264;3,20;26948;8,00;44194;2,70;483705;2,20;KT 917 3074;; +23041;MARDI;23/05/2023;5;22/08/2023;34;5;14;6;23;11;2;-5-6-14-23-34-;-2-11-;0;0;0;0;3;187039,00;2;7;18734,60;9;38;1074,90;136;710;105,90;269;1377;57,70;405;1826;30,60;4100;19268;14,50;6649;30860;10,10;16528;74395;7,80;20482;98805;7,10;88535;417707;5,30;216974;992176;3,50;0;0;4;506,60;61;18,40;133;8,40;1922;1,40;2966;2,50;9587;3,60;15064;10,70;40059;2,20;301298;2,60;BQ 269 5299;; +23040;VENDREDI;19/05/2023;4;18/08/2023;41;27;34;49;18;3;9;-18-27-34-41-49-;-3-9-;0;0;0;1;3;239206,30;3;10;16771,90;12;44;1187,20;185;854;112,60;490;2169;46,90;380;1724;41,40;6448;30151;11,80;7236;34696;11,40;14818;70468;10,50;35099;164870;5,40;100229;491058;5,70;208853;1016739;4,40;0;0;4;645,60;81;17,40;210;6,80;2854;1,20;3199;2,90;15294;2,90;28001;7,40;45189;2,50;452667;2,20;KL 890 9887;; +23039;MARDI;16/05/2023;3;15/08/2023;14;6;9;4;37;4;11;-4-6-9-14-37-;-4-11-;0;0;0;0;1;539367,90;0;7;18008,40;3;24;1636,00;162;619;116,80;276;1249;61,20;347;1378;38,90;3728;17659;15,20;6871;28842;10,30;15689;65918;8,40;18383;90548;7,40;87718;392797;5,40;211872;921483;3,70;0;0;3;680,40;80;14,10;139;8,10;1783;1,50;3167;2,30;8795;4,00;13263;12,30;40352;2,20;296464;2,70;LB 512 4777;; +23038;VENDREDI;12/05/2023;2;11/08/2023;9;44;13;39;10;6;4;-9-10-13-39-44-;-4-6-;0;0;0;2;3;234925,90;2;8;20589,70;9;36;1425,10;148;676;139,80;480;1824;54,70;319;1446;48,50;7995;30108;11,60;7871;34193;11,40;17502;76574;9,50;41166;158399;5,50;123693;530715;5,20;273957;1183027;3,70;1;1519,90;5;243,10;64;23,30;208;7,20;3439;1,00;3533;2,80;17735;2,60;25287;8,60;55647;2,20;421268;2,50;RI 034 8136;; +23037;MARDI;09/05/2023;1;08/08/2023;21;28;13;17;46;9;7;-13-17-21-28-46-;-7-9-;0;0;0;0;0;0;0;5;126107,90;8;42;885,80;164;851;80,50;508;2057;35,20;336;1477;34,40;6106;25727;9,80;7960;35739;7,90;14439;64835;8,10;28427;120240;5,30;99796;455562;4,40;184579;859732;3,70;0;0;3;639,20;87;11,90;227;4,60;2747;,90;3566;1,90;12728;2,60;19129;8,00;44912;1,90;322473;2,30;SZ 660 9457;; +23036;VENDREDI;05/05/2023;12;04/08/2023;34;8;49;18;3;3;7;-3-8-18-34-49-;-3-7-;1;3;53041075,00;0;7;157711,00;1;9;28668,60;13;69;1164,70;306;1270;116,50;1040;4046;38,60;564;2521;43,60;15370;60931;9,00;13862;57893;10,50;25518;113767;10,00;80139;325394;4,20;201865;837510;5,20;379141;1672707;4,10;0;0;4;1067,10;126;18,30;433;5,40;6490;,90;6122;2,50;33287;2,20;50415;6,70;88440;2,10;739387;2,30;SR 029 2197;; +23035;MARDI;02/05/2023;11;01/08/2023;48;44;32;7;47;8;7;-7-32-44-47-48-;-7-8-;0;0;0;0;2;383454,10;3;6;29873,10;8;27;2067,70;168;759;135,40;388;1705;63,70;251;1469;52,00;5357;24471;15,60;7829;37168;11,40;14796;72324;10,90;29366;132698;7,20;120033;553646;5,40;224505;1069150;4,50;0;0;6;469,90;72;21,10;178;8,70;2321;1,60;3448;3,00;12691;3,80;21444;10,50;52229;2,40;516064;2,10;BY 030 2805;; +23034;VENDREDI;28/04/2023;10;28/07/2023;34;23;11;13;16;1;10;-11-13-16-23-34-;-1-10-;0;0;0;1;1;982720,10;3;14;16405,50;11;49;1459,90;210;1194;110,30;420;2040;68,20;678;3272;29,90;6195;29204;16,70;10933;51296;10,60;29304;140130;7,20;30817;147338;8,30;149809;697582;5,50;399529;1866740;3,30;0;0;2;1736,70;87;21,60;189;10,10;2892;1,60;4819;2,60;14435;4,10;22518;12,30;65927;2,30;494050;2,70;QG 416 7056;; +23033;MARDI;25/04/2023;9;25/07/2023;29;45;40;10;30;12;1;-10-29-30-40-45-;-1-12-;0;0;0;0;1;717775,80;2;5;33551,20;6;40;1306,20;111;648;148,50;323;1717;59,20;316;1695;42,10;3908;19750;18,10;6019;31233;12,70;15541;79582;9,30;22298;108657;8,20;84624;418973;6,70;228710;1119406;4,00;0;0;3;830,40;48;28,80;143;9,40;1839;1,80;2690;3,30;10279;4,10;17876;11,10;38223;2,90;353305;2,80;TY 132 5543;; +23032;VENDREDI;21/04/2023;8;21/07/2023;18;8;7;42;33;8;2;-7-8-18-33-42-;-2-8-;0;0;0;1;3;318105,80;3;13;17156,90;16;75;926,20;259;1279;100,00;782;3538;38,20;596;2747;34,60;10227;47595;9,90;11565;54053;9,80;23518;113805;8,60;50027;234770;5,00;155134;745626;5,00;329020;1600549;3,70;0;0;7;459,10;125;14,00;322;5,40;4232;1,00;4986;2,30;21051;2,60;30046;8,50;67071;2,10;494059;2,50;IK 847 8978;; +23031;MARDI;18/04/2023;7;18/07/2023;37;21;17;35;23;11;10;-17-21-23-35-37-;-10-11-;0;0;0;0;4;150414,50;0;6;23436,20;9;37;1183,70;148;645;125,00;215;1083;78,70;340;1682;35,60;2985;16568;18,00;5791;29365;11,30;15690;79242;7,80;15589;84684;8,90;79822;402950;5,80;219763;1089552;3,50;0;0;6;356,80;78;15,20;115;10,00;1399;2,00;2721;2,80;7523;4,80;12477;13,60;37004;2,60;307425;2,70;BP 573 2097;; +23030;VENDREDI;14/04/2023;6;14/07/2023;44;4;37;49;11;7;6;-4-11-37-44-49-;-6-7-;0;0;0;1;2;383595,60;0;7;25615,00;8;30;1861,60;194;867;118,60;343;1645;66,10;333;1677;45,50;5562;25265;15,10;8609;37902;11,20;15959;77447;10,20;31227;141622;6,70;127362;560348;5,40;235391;1123822;4,30;0;0;3;943,30;88;17,80;142;10,70;2471;1,50;3846;2,70;13975;3,50;23792;9,50;57086;2,20;527348;2,10;BP 590 6741;; +23029;MARDI;11/04/2023;5;11/07/2023;19;16;14;29;10;2;10;-10-14-16-19-29-;-2-10-;0;0;0;1;2;269932,10;1;10;12617,50;13;33;1190,90;171;790;91,60;323;1498;51,00;452;1874;28,60;4419;19088;14,00;7245;31615;9,40;16828;74016;7,50;21374;94853;7,10;91864;409669;5,20;215858;975578;3,50;0;0;4;490,80;60;17,30;150;7,20;2080;1,30;3290;2,20;10228;3,30;14774;10,60;41477;2,10;290245;2,70;QB 155 9413;; +23028;VENDREDI;07/04/2023;4;07/07/2023;26;28;46;5;36;12;6;-5-26-28-36-46-;-6-12-;0;0;0;1;2;352535,10;1;6;27464,40;9;33;1555,30;156;664;142,30;337;1641;60,90;331;1594;44,00;4429;19377;18,10;6638;30798;12,70;15807;71565;10,10;24620;105720;8,30;94997;429199;6,40;242286;1083158;4,10;1;1529,80;4;305,90;78;19,30;172;8,80;2177;1,70;3151;3,20;12063;3,90;20614;10,70;44887;2,70;417412;2,60;TM 201 9996;; +23027;MARDI;04/04/2023;3;04/07/2023;50;33;16;31;10;3;8;-10-16-31-33-50-;-3-8-;0;0;0;0;2;264328,70;2;5;24711,10;2;16;2405,20;99;475;149,20;282;1348;55,50;215;1112;47,30;4292;22024;11,90;4905;24416;12,00;10571;51848;10,50;24842;123299;5,30;78233;374844;5,50;163934;785097;4,20;0;0;0;0;45;66,00;119;9,20;1880;1,40;2266;3,20;11018;3,10;19520;8,20;35733;2,50;347517;2,20;BT 280 0928;; +23026;VENDREDI;31/03/2023;2;30/06/2023;28;34;47;16;18;10;5;-16-18-28-34-47-;-5-10-;0;0;0;1;2;346909,70;0;3;54052,30;6;27;1870,60;126;676;137,60;286;1514;64,90;313;1501;46,00;4485;22272;15,50;6331;31227;12,30;14911;70706;10,10;24199;119432;7,20;97357;463717;5,90;227267;1058817;4,10;1;1455,40;4;291,00;62;23,40;152;9,40;2095;1,70;2976;3,20;11540;3,90;19870;10,50;44880;2,60;415515;2,50;VQ 025 4030;; +23025;MARDI;28/03/2023;1;27/06/2023;21;16;34;36;44;9;10;-16-21-34-36-44-;-9-10-;0;0;0;0;1;482807,50;0;4;28210,00;6;19;1849,80;101;473;136,80;136;779;87,80;202;1050;45,80;2143;11497;20,90;3893;20191;13,20;9119;47747;10,40;12128;63320;9,50;60278;304758;6,20;141724;713672;4,30;0;0;1;1795,20;52;18,80;63;15,50;1047;2,30;1839;3,60;5952;5,20;10420;13,80;28195;2,80;291908;2,40;KX 475 0920;; +23024;VENDREDI;24/03/2023;5;23/06/2023;36;25;5;46;12;10;6;-5-12-25-36-46-;-6-10-;0;1;66287741,00;1;6;126453,80;2;7;25332,20;8;34;1624,40;192;864;117,70;350;1710;62,80;379;1896;39,80;4871;24195;15,60;7623;34942;12,00;18742;85287;9,20;25894;125436;7,50;111615;506644;5,90;267729;1225880;3,90;0;0;3;926,40;89;17,30;168;9,00;2323;1,60;3457;2,90;12026;3,90;20268;11,00;50869;2,40;435326;2,50;CY 773 8015;; +23023;MARDI;21/03/2023;4;20/06/2023;29;1;9;32;20;3;2;-1-9-20-29-32-;-2-3-;0;0;0;0;2;281195,20;0;6;21906,60;5;28;1462,10;182;711;106,00;275;1360;58,60;339;1379;40,60;4089;19222;14,50;7151;31063;10,00;13856;61825;9,40;22479;102509;6,80;100233;442730;5,00;194132;881019;4,00;0;0;3;698,10;89;12,90;134;8,60;1882;1,50;3276;2,30;10341;3,50;15472;10,80;44962;2,10;365837;2,20;KH 270 8685;; +23022;VENDREDI;17/03/2023;3;16/06/2023;37;5;26;15;49;8;11;-5-15-26-37-49-;-8-11-;0;0;0;2;7;103234,90;3;10;16889,30;7;31;1696,90;143;741;130,70;297;1409;72,70;402;1816;39,60;4480;21143;17,00;7125;33206;12,00;17214;77603;9,60;23571;112699;8,00;103890;478919;5,90;256954;1136779;4,00;1;1561,90;5;249,90;62;25,10;166;9,40;2214;1,70;3356;3,00;11560;4,20;19098;11,80;48003;2,60;434924;2,50;QJ 547 2111;; +23021;MARDI;14/03/2023;2;13/06/2023;26;46;28;31;16;12;11;-16-26-28-31-46-;-11-12-;0;0;0;2;3;176354,00;2;6;20608,40;7;27;1426,40;113;472;150,30;185;890;84,20;314;1289;40,80;2542;12806;20,50;4462;21120;13,90;13290;59941;9,10;13718;68655;9,60;63198;299524;6,90;193051;853081;3,90;1;1156,90;3;308,50;50;23,10;97;11,60;1300;2,10;2183;3,50;7031;5,10;11846;14,00;30840;3,00;291066;2,80;BO 892 9850;; +23020;VENDREDI;10/03/2023;1;09/06/2023;9;16;48;37;38;7;2;-9-16-37-38-48-;-2-7-;0;0;0;0;4;179080,60;1;3;55805,40;9;35;1489,80;140;620;154,90;453;1879;54,00;285;1395;51,10;7408;29236;12,20;7011;30605;13,00;14194;64889;11,40;45867;172837;5,10;111311;481535;5,80;219649;993900;4,50;0;0;3;975,20;70;22,50;198;8,00;3363;1,10;3230;3,30;20048;2,50;35607;6,60;51495;2,50;528511;2,20;JS 582 4093;; +23019;MARDI;07/03/2023;3;06/06/2023;50;24;35;13;11;6;11;-11-13-24-35-50-;-6-11-;0;1;144966361,00;0;5;186010,40;4;11;19760,70;11;52;1302,00;213;1066;116,90;495;2235;58,90;460;2142;43,20;7940;32721;14,10;10563;44979;11,40;23875;103188;9,30;39997;164414;7,00;155477;655921;5,50;362067;1519654;3,80;0;0;5;758,10;81;25,90;242;8,60;3595;1,40;4793;2,90;17958;3,60;28834;10,40;69417;2,40;577363;2,60;KL 200 9176;; +23018;VENDREDI;03/03/2023;2;02/06/2023;24;3;8;50;6;10;11;-3-6-8-24-50-;-10-11-;0;0;0;4;9;150850,40;8;23;13795,90;18;71;1392,00;445;1574;115,60;712;2768;69,50;1275;4170;32,40;9015;38046;17,70;16849;63197;11,90;47840;175575;7,90;46824;197887;8,50;230675;887391;6,00;642413;2391851;3,60;1;3350,90;10;268,00;197;17,00;323;10,20;4344;1,90;7709;2,80;22253;4,60;36391;13,20;106475;2,50;878163;2,70;VB 581 8468;; +23017;MARDI;28/02/2023;1;30/05/2023;29;33;12;37;47;10;5;-12-29-33-37-47-;-5-10-;0;0;0;2;5;104365,10;1;10;12195,90;7;29;1309,90;104;522;134,00;247;1167;63,30;237;1123;46,20;3427;16880;15,30;5091;23943;12,10;12688;55554;9,70;18412;89949;7,20;74127;345266;5,90;175090;795288;4,10;1;1113,10;1;890,50;45;24,70;119;9,30;1597;1,70;2321;3,10;8599;4,00;15134;10,60;34131;2,60;315643;2,50;KE 116 4078;; +23016;VENDREDI;24/02/2023;7;26/05/2023;42;34;7;23;48;1;3;-7-23-34-42-48-;-1-3-;1;1;102242065,00;0;2;446486,90;2;11;18972,90;15;65;1000,00;305;1295;92,40;432;1942;65,10;661;2780;31,90;6410;29277;15,10;10825;48033;10,30;23124;103245;8,90;35931;158336;7,00;146804;653739;5,30;316184;1414630;4,00;0;0;6;565,40;126;14,90;189;9,80;2796;1,60;4622;2,70;15555;3,70;26315;10,30;64153;2,30;563257;2,40;VC 786 7371;; +23015;MARDI;21/02/2023;6;23/05/2023;49;41;22;34;21;2;7;-21-22-34-41-49-;-2-7-;0;0;0;1;2;305559,40;0;3;47609,50;8;37;1202,30;134;591;138,60;332;1597;54,20;283;1240;49,00;5607;24718;12,30;5649;27372;12,40;11337;57167;11,00;34212;145813;5,20;87054;418022;5,70;171210;861278;4,50;0;0;2;1088,60;73;16,50;140;8,50;2374;1,20;2447;3,20;14599;2,50;26157;6,60;38297;2,50;398432;2,10;AE 744 7635;; +23014;VENDREDI;17/02/2023;5;19/05/2023;42;27;45;23;8;1;9;-8-23-27-42-45-;-1-9-;0;0;0;0;2;389007,50;3;10;18183,40;8;38;1490,40;193;915;114,00;434;1931;57,10;435;2131;36,30;6125;28368;13,60;8292;39444;10,90;21257;95550;8,40;31991;146242;6,60;116722;553040;5,50;285543;1322109;3,70;0;0;2;1410,80;84;18,00;191;8,00;2493;1,50;3663;2,80;13475;3,60;22444;10,00;51534;2,40;433737;2,50;BQ 304 7323;; +23013;MARDI;14/02/2023;4;16/05/2023;43;24;46;26;38;2;3;-24-26-38-43-46-;-2-3-;0;0;0;0;4;147832,90;1;2;69102,00;2;21;2049,80;132;611;129,70;236;1078;77,70;220;1144;51,40;3452;16539;17,80;5676;25640;12,80;11375;53177;11,50;20412;95339;7,70;86458;392777;5,90;173898;803215;4,60;0;0;1;2311,40;65;19,40;110;11,60;1683;1,80;2652;3,20;9561;4,10;19128;9,60;39763;2,60;433736;2,10;JF 732 7313;; +23012;VENDREDI;10/02/2023;3;12/05/2023;3;13;36;25;47;3;8;-3-13-25-36-47-;-3-8-;0;0;0;4;12;62801,60;4;17;10360,70;15;83;660,90;216;1151;87,80;529;3184;33,50;390;2099;35,70;7478;35034;10,70;8894;41898;9,90;17258;82058;9,50;37924;176046;5,30;129676;592301;5,00;250918;1160251;4,10;1;1604,80;8;160,40;88;18,20;241;6,60;3219;1,20;3986;2,60;16699;3,00;26424;8,70;58779;2,20;486536;2,30;QO 520 2312;; +23011;MARDI;07/02/2023;2;09/05/2023;8;47;34;44;2;9;3;-2-8-34-44-47-;-3-9-;0;0;0;0;5;109249,20;2;6;21277,70;5;19;2092,80;96;447;163,80;267;1268;61,00;219;1050;51,80;4411;20488;13,20;4624;22536;13,40;10345;49757;11,30;27055;121583;5,60;75929;361349;5,90;167953;778049;4,40;0;0;3;697,50;46;25,20;123;9,20;1969;1,40;2142;3,50;12160;2,90;22947;7,30;35177;2,60;365300;2,20;SL 643 1128;; +23010;VENDREDI;03/02/2023;1;05/05/2023;14;32;45;2;17;10;3;-2-14-17-32-45-;-3-10-;0;0;0;4;7;200626,30;9;30;10940,90;35;97;1053,90;443;1623;116,00;1139;3561;55,90;1125;4033;34,60;15277;49800;14,00;20104;68391;11,40;46148;159540;9,10;81348;261401;6,70;285482;969875;5,70;658299;2249589;3,90;3;1274,60;16;191,10;185;20,40;459;8,20;6599;1,40;8733;2,80;35527;3,30;60004;9,10;124936;2,40;1105837;2,40;QL 526 5035;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,; +23009;MARDI;31/01/2023;5;02/05/2023;7;12;33;43;19;3;7;-7-12-19-33-43-;-3-7-;0;1;66081021,00;1;3;202795,00;4;5;28437,90;14;99;447,30;231;972;83,90;856;3897;22,10;374;1722;35,10;10757;47231;6,40;10051;42115;8,00;16514;74951;8,30;45150;199918;3,80;125805;544979;4,40;216465;1000376;3,80;0;0;10;220,30;100;12,20;390;3,10;4558;,60;4512;1,80;19352;1,90;25262;7,00;55147;1,80;362021;2,40;PB 840 6401;; +23008;VENDREDI;27/01/2023;4;28/04/2023;15;19;31;8;9;12;10;-8-9-15-19-31-;-10-12-;0;0;0;2;4;186490,50;5;13;13411,00;12;33;1645,50;204;797;125,50;314;1505;70,20;647;2624;28,30;3992;20087;18,40;7356;33629;12,30;25928;111310;6,90;19763;101419;9,20;99353;465139;6,30;330382;1474197;3,20;2;734,20;6;195,80;97;14,90;153;9,40;1934;1,80;3458;2,80;9529;4,70;15681;13,50;46757;2,50;350905;3,00;UF 710 0564;; +23007;MARDI;24/01/2023;3;25/04/2023;11;27;47;41;43;7;11;-11-27-41-43-47-;-7-11-;0;0;0;1;2;263096,40;0;4;30744,90;6;34;1126,60;94;496;142,20;274;1315;56,70;206;1076;48,70;3622;19025;13,70;4774;24315;12,00;9914;50173;10,80;19617;98362;6,70;72417;360151;5,70;148156;748772;4,40;0;0;1;1842,50;37;27,60;120;8,50;1763;1,40;2157;3,10;9063;3,50;14806;9,90;32674;2,50;311111;2,30;FU 933 3436;; +23006;VENDREDI;20/01/2023;2;21/04/2023;40;6;13;42;3;7;11;-3-6-13-40-42-;-7-11-;0;0;0;1;4;170372,20;0;8;19909,40;7;36;1378,00;155;726;125,80;324;1649;58,50;343;1588;42,70;5074;24580;13,80;7520;34871;10,80;17031;78980;8,90;26057;127330;6,70;110182;506287;5,30;241746;1128008;3,80;0;0;3;816,10;76;17,80;151;9,00;2367;1,40;3315;2,70;11868;3,50;19357;10,10;48952;2,20;399861;2,40;MO 149 0237;; +23005;MARDI;17/01/2023;1;18/04/2023;18;46;30;50;21;2;6;-18-21-30-46-50-;-2-6-;0;0;0;3;6;82263,60;2;3;38452,70;12;30;1197,70;90;415;159,40;253;1174;59,60;176;962;51,10;4029;18805;13,00;4228;21186;12,90;9086;46272;11,00;23521;107208;5,70;68528;330237;5,80;144638;717298;4,30;1;1005,10;6;134,00;53;18,60;115;8,70;1728;1,40;1974;3,30;10278;3,00;18318;7,90;30926;2,60;315717;2,20;PM 861 1466;; +23004;VENDREDI;13/01/2023;5;14/04/2023;13;4;33;25;37;10;5;-4-13-25-33-37-;-5-10-;0;1;68825114,00;4;5;182354,40;4;11;19372,30;15;66;1005,60;310;1124;108,70;586;2343;55,10;775;2818;32,20;7897;31940;14,20;12765;48995;10,30;31055;116575;8,00;40578;163100;7,00;170930;667206;5,30;413948;1566569;3,60;2;1103,90;7;252,30;132;16,40;238;9,10;3519;1,50;5355;2,70;18270;3,70;29106;10,90;73934;2,40;606899;2,60;JN 226 6277;; +23003;MARDI;10/01/2023;4;11/04/2023;42;11;34;13;40;10;3;-11-13-34-40-42-;-3-10-;0;0;0;0;0;0;1;5;145098,90;6;28;1528,80;105;552;142,80;308;1334;62,40;260;1342;43,60;3931;19941;14,60;5054;25943;12,50;11406;59495;10,20;21476;105242;7,00;76496;398664;5,80;175618;911043;4,10;0;0;2;990,30;49;21,50;145;7,30;1854;1,40;2291;3,10;10028;3,40;16673;9,50;34808;2,50;317355;2,40;CQ 491 0323;; +23002;VENDREDI;06/01/2023;3;07/04/2023;44;16;35;45;31;12;4;-16-31-35-44-45-;-4-12-;0;0;0;1;2;342205,30;0;0;0;4;21;9989,50;96;535;171,50;249;1290;75,20;260;1362;50,00;3306;17414;19,50;4992;25411;14,90;12801;63472;11,10;19088;94055;9,10;80744;390026;6,90;207277;986599;4,40;1;1426,40;1;1141,10;42;31,60;119;11,70;1641;2,10;2305;4,00;9276;4,70;16224;12,60;37485;3,10;396752;2,50;PJ 372 0439;; +23001;MARDI;03/01/2023;2;04/04/2023;20;29;21;46;45;10;3;-20-21-29-45-46-;-3-10-;0;0;0;0;2;260515,80;0;5;24354,70;3;21;1806,10;86;400;174,60;196;923;80,00;170;903;57,40;3378;15877;16,30;4060;20261;14,20;9049;46476;11,50;19658;90728;7,10;68362;331619;6,20;156102;755050;4,30;0;0;2;942,00;50;20,90;100;10,00;1545;1,60;1875;3,60;9104;3,50;15830;9,50;31672;2,70;306552;2,40;NP 521 2967;; +22104;VENDREDI;30/12/2022;1;31/03/2023;48;42;17;34;47;1;9;-17-34-42-47-48-;-1-9-;0;0;0;0;4;177017,30;1;7;23641,00;13;45;1145,40;118;575;165,10;268;1466;68,40;293;1419;49,70;4279;21194;16,60;5647;29331;13,40;13561;68285;10,70;25675;123442;7,10;84087;434802;6,40;199466;1011708;4,40;0;0;6;421,20;62;22,20;116;12,00;1864;1,80;2523;3,60;11271;3,80;22001;9,20;38593;2,90;421925;2,30;HJ 777 0024;; +22103;MARDI;27/12/2022;2;28/03/2023;33;31;19;34;43;5;3;-19-31-33-34-43-;-3-5-;0;1;26956990,00;0;2;259877,40;1;1;121475,20;8;32;1182,30;115;518;134,50;252;1355;54,30;213;1015;51,00;3947;20900;12,30;4779;24579;11,70;9867;48772;11,00;24573;120871;5,30;72183;363093;5,60;146691;715483;4,60;0;0;3;626,50;57;18,30;116;8,80;1651;1,50;2135;3,20;10581;3,00;19127;7,80;32319;2,60;333530;2,20;DZ 394 5017;; +22102;VENDREDI;23/12/2022;1;24/03/2023;16;17;26;23;35;5;11;-16-17-23-26-35-;-5-11-;0;0;0;1;2;363954,60;3;10;17012,40;6;34;1558,50;140;806;121,10;334;1846;55,80;370;1817;39,90;4493;25410;14,20;7553;39914;10,10;18088;88854;8,40;23713;130022;7,00;107850;548914;5,20;259306;1237331;3,70;0;0;5;518,70;65;22,10;173;8,20;2155;1,60;3285;2,90;11062;4,00;17838;11,60;48044;2,40;390092;2,60;VM 500 1739;; +22101;MARDI;20/12/2022;4;21/03/2023;1;20;11;17;27;4;5;-1-11-17-20-27-;-4-5-;0;1;51605931,00;2;7;81968,40;6;9;14900,10;15;52;803,20;289;1077;71,40;413;1916;42,40;542;2197;26,00;4950;23778;12,00;9739;40581;7,80;20505;84067;7,00;22467;111476;6,40;109444;512306;4,40;225774;1034823;3,50;2;553,10;5;177,00;128;8,50;181;6,00;2120;1,20;4202;1,70;9418;3,60;13913;11,40;47119;1,90;306347;2,50;ET 088 9165;; +22100;VENDREDI;16/12/2022;3;17/03/2023;2;15;44;35;19;7;2;-2-15-19-35-44-;-2-7-;0;0;0;1;2;354267,10;1;4;41399,00;25;55;937,80;188;810;117,30;659;2454;40,90;328;1565;45,10;9100;35975;9,80;8337;37644;10,40;15028;72341;10,10;45255;180570;4,90;123280;553257;5,00;224173;1067805;4,20;0;0;7;373,90;86;16,90;285;5,00;3856;,90;3676;2,60;18782;2,40;29448;7,10;54535;2,10;455532;2,20;LY 901 6311;; +22099;MARDI;13/12/2022;2;14/03/2023;30;3;12;26;9;10;11;-3-9-12-26-30-;-10-11-;0;0;0;1;4;131029,00;5;17;7205,50;6;23;1658,80;215;780;90,10;263;1068;69,50;784;2764;18,80;2838;14135;18,40;7073;28374;10,20;24353;92344;5,80;13949;71516;9,10;80321;353831;5,80;255115;1065206;3,10;0;0;3;660,80;98;11,10;110;9,90;1359;1,90;3224;2,20;6739;5,00;10826;14,60;37973;2,30;270813;2,90;OG 732 2723;; +22098;VENDREDI;09/12/2022;1;10/03/2023;8;46;31;27;50;3;1;-8-27-31-46-50-;-1-3-;0;0;0;0;2;343883,00;1;6;26790,30;9;35;1430,40;126;584;157,90;323;1422;68,50;283;1432;47,80;4811;21477;15,90;6076;29299;13,00;13721;64789;10,90;27525;121538;7,00;100430;452332;6,00;217802;999745;4,30;0;0;3;899,30;50;29,30;157;9,40;2191;1,60;2767;3,50;12236;3,80;21237;10,10;45569;2,60;458109;2,30;MH 156 7284;; +22097;MARDI;06/12/2022;9;07/03/2023;20;25;27;26;12;8;12;-12-20-25-26-27-;-8-12-;0;1;142897164,00;1;5;160293,30;2;4;46828,90;10;61;956,40;196;902;119,10;375;1737;65,40;572;2335;34,10;5356;24294;16,40;10010;41422;10,70;25864;104275;7,90;27091;123593;8,10;135001;576321;5,40;346444;1447771;3,50;0;0;5;599,90;101;16,10;162;10,10;2505;1,60;4524;2,40;12652;4,00;18659;12,80;59984;2,20;428589;2,70;BF 128 8115;; +22096;VENDREDI;02/12/2022;8;03/03/2023;39;12;35;21;45;11;6;-12-21-35-39-45-;-6-11-;0;0;0;0;1;1174364,10;1;13;21112,90;17;62;1378,80;250;1122;140,30;619;2555;65,10;610;2779;42,00;8946;38067;15,30;11991;53798;12,10;27936;123299;9,80;48142;202760;7,20;178792;776392;5,90;422194;1820950;4,00;0;0;5;927,20;108;23,60;266;9,50;4130;1,50;5345;3,10;21690;3,60;36230;10,20;79779;2,60;727880;2,50;HR 390 6385;; +22095;MARDI;29/11/2022;7;28/02/2023;20;38;35;15;24;12;8;-15-20-24-35-38-;-8-12-;0;0;0;0;1;614404,50;1;1;143596,40;4;24;1863,60;110;552;149,20;217;1051;82,80;258;1405;43,50;2974;16173;18,90;4964;25966;13,10;12220;64037;9,90;16744;86791;8,80;76454;389623;6,20;195924;972239;4,00;0;0;2;1066,90;63;18,80;100;11,60;1476;1,90;2251;3,40;7955;4,60;13579;12,50;34880;2,70;319577;2,60;TV 773 5123;; +22094;VENDREDI;25/11/2022;6;24/02/2023;21;19;39;35;37;2;6;-19-21-35-37-39-;-2-6-;0;0;0;0;4;192883,50;2;10;18032,00;8;39;1440,10;166;812;127,40;408;1986;55,00;324;1687;45,50;6368;30221;12,70;7107;35846;11,90;14688;78160;10,20;35360;164651;5,80;107020;533344;5,70;222500;1156230;4,20;0;0;2;1329,40;77;19,10;177;8,20;2746;1,30;3098;3,10;15138;3,00;26243;8,10;46635;2,50;458023;2,30;GA 666 0427;; +22093;MARDI;22/11/2022;5;21/02/2023;24;29;42;21;22;3;11;-21-22-24-29-42-;-3-11-;0;0;0;0;2;280000,80;1;3;43627,20;7;20;2038,30;106;503;149,20;257;1214;65,30;223;1170;47,60;3849;19094;14,60;5453;26626;11,60;12469;60413;9,50;20302;101554;6,90;84953;411289;5,30;194240;921198;3,80;0;0;1;1968,40;54;19,50;131;8,10;1754;1,50;2467;2,90;9395;3,60;14382;10,90;37947;2,30;304584;2,50;MU 402 1458;; +22092;VENDREDI;18/11/2022;4;17/02/2023;41;13;35;19;16;11;1;-13-16-19-35-41-;-1-11-;0;0;0;0;3;245766,10;1;11;15665,30;6;33;1626,40;125;658;150,20;301;1424;73,30;345;1722;42,60;4257;21279;17,20;6169;30629;13,30;15967;78666;9,60;23277;114630;8,00;95090;463190;6,20;253264;1201485;3,90;0;0;3;885,20;55;26,30;137;10,70;1939;1,80;2818;3,40;11071;4,10;18050;11,80;42993;2,80;385170;2,70;HK 181 5153;; +22091;MARDI;15/11/2022;3;14/02/2023;31;21;13;24;33;1;12;-13-21-24-31-33-;-1-12-;0;0;0;0;3;179655,80;2;6;20994,20;7;25;1569,40;136;595;121,40;269;1176;64,90;405;1932;27,70;3430;16057;16,70;5751;26062;11,40;17118;79418;7,00;17332;80918;8,30;73409;348245;6,10;218204;1026922;3,30;0;0;3;659,80;62;17,70;133;8,00;1708;1,50;2554;2,80;8589;3,90;13736;11,50;34393;2,60;272601;2,80;RT 647 3979;; +22090;VENDREDI;11/11/2022;2;10/02/2023;25;2;19;44;24;2;10;-2-19-24-25-44-;-2-10-;0;0;0;1;3;230878,40;0;8;20235,00;10;35;1440,60;162;764;121,50;368;1607;61,10;359;1585;43,50;5188;23625;14,60;7576;35260;10,90;16405;78973;9,00;25239;117657;7,30;107188;509901;5,30;241565;1164650;3,70;1;1343,40;3;358,20;80;16,70;167;8,00;2414;1,30;3432;2,50;11674;3,50;17522;11,00;48268;2,20;360109;2,60;IG 959 4548;; +22089;MARDI;08/11/2022;1;07/02/2023;50;32;33;5;3;1;8;-3-5-32-33-50-;-1-8-;0;0;0;0;3;173618,20;0;3;40577,40;2;15;2527,70;76;451;154,80;204;979;75,40;166;1108;46,80;3386;16606;15,60;4270;22369;12,90;9777;51701;10,40;19852;90609;7,20;70913;355759;5,70;164212;821545;4,00;0;0;1;1955,10;41;26,40;107;10,00;1572;1,70;2000;3,50;9082;3,70;15621;10,00;33064;2,60;312893;2,40;AA 807 8409;; +22088;VENDREDI;04/11/2022;12;03/02/2023;11;2;37;47;45;2;3;-2-11-37-45-47-;-2-3-;1;1;160788895,00;0;2;572462,00;3;10;26758,70;9;51;1634,20;221;1100;139,50;432;2139;75,80;418;2259;50,40;6795;33996;16,70;10516;51290;12,40;21038;104682;11,30;39533;193417;7,40;167359;797170;5,60;325505;1616411;4,50;0;0;5;779,00;91;23,20;190;11,20;2951;1,80;4620;3,10;17022;3,90;31011;10,00;71799;2,40;695595;2,20;OX 399 4093;; +22087;MARDI;01/11/2022;11;31/01/2023;42;48;19;37;47;6;1;-19-37-42-47-48-;-1-6-;0;0;0;0;2;370898,30;2;8;21671,20;5;25;2160,00;113;626;158,90;265;1347;78,00;267;1439;51,30;4341;22587;16,30;5332;28884;14,20;12774;70570;10,80;25976;126246;7,30;86787;446372;6,50;203623;1075611;4,30;0;0;1;2431,40;53;25,00;105;12,30;1926;1,70;2353;3,80;11418;3,60;20904;9,30;37741;2,90;407337;2,30;AE 147 7153;; +22086;VENDREDI;28/10/2022;10;27/01/2023;28;35;21;16;14;11;1;-14-16-21-28-35-;-1-11-;0;0;0;1;1;948590,30;2;16;13856,30;13;73;945,90;388;2319;54,80;403;1966;68,40;1195;7431;12,70;5293;27405;17,20;9083;45029;11,70;25831;124632;7,80;28271;144772;8,20;125489;618311;6,00;341520;1628382;3,70;0;0;6;541,70;157;11,40;190;9,30;2319;1,90;3926;3,00;12795;4,30;21487;12,10;54651;2,60;473486;2,70;HB 963 3673;; +22085;MARDI;25/10/2022;9;24/01/2023;24;7;46;34;39;6;1;-7-24-34-39-46-;-1-6-;0;0;0;0;2;357879,70;1;5;33456,90;8;32;1628,20;102;643;149,20;307;1407;72,10;249;1534;46,40;4544;22103;16,10;5899;29347;13,50;13869;72763;10,10;26703;122624;7,30;94646;453110;6,20;223075;1095415;4,10;0;0;2;1239,40;47;29,30;139;9,60;1969;1,70;2586;3,50;11606;3,60;20179;9,80;41164;2,70;402337;2,40;EQ 472 6266;; +22084;VENDREDI;21/10/2022;8;20/01/2023;26;28;38;25;40;1;6;-25-26-28-38-40-;-1-6-;0;0;0;0;2;427941,30;1;11;18184,80;9;35;1780,10;182;829;138,40;338;1731;70,00;378;1865;45,70;5644;26235;16,20;7725;36432;13,00;17540;85805;10,30;32775;146703;7,30;121242;558724;6,00;278717;1324150;4,10;0;0;2;1549,00;80;21,20;140;12,00;2427;1,70;3352;3,30;13912;3,80;24544;10,10;52180;2,60;500513;2,40;QY 962 4703;; +22083;MARDI;18/10/2022;7;17/01/2023;42;3;5;48;13;6;1;-3-5-13-42-48-;-1-6-;0;0;0;1;4;155132,30;1;5;29005,50;7;31;1457,10;133;595;139,80;295;1372;64,10;329;1504;41,10;4731;21352;14,40;6798;30673;11,20;16312;73614;8,70;24005;108701;7,10;101912;451977;5,40;233147;1064813;3,70;1;1243,30;4;248,60;64;19,40;138;8,90;2012;1,50;2912;2,80;10118;3,80;16616;10,70;44292;2,20;344473;2,50;JK 323 2687;; +22082;VENDREDI;14/10/2022;6;13/01/2023;28;48;29;24;34;7;3;-24-28-29-34-48-;-3-7-;0;0;0;1;3;261311,70;1;4;45804,60;10;47;1214,20;158;684;153,60;452;2196;50,60;293;1505;51,80;8601;37816;10,30;6987;33592;12,90;13410;69182;11,70;54488;233505;4,20;116869;542207;5,70;223033;1099185;4,50;1;1560,10;4;312,00;74;20,80;197;7,60;3455;1,10;3156;3,20;22163;2,10;38185;5,90;51124;2,40;510275;2,10;VE 527 2435;; +22081;MARDI;11/10/2022;5;10/01/2023;40;35;8;32;23;8;1;-8-23-32-35-40-;-1-8-;0;0;0;0;1;592272,50;0;5;27684,70;2;27;1596,80;136;677;117,30;235;1308;64,10;310;1654;35,60;3856;18717;15,70;5495;28746;11,40;12453;64639;9,40;21819;102097;7,20;78820;395850;5,90;178518;900105;4,10;0;0;1;2116,70;64;18,00;104;10,80;1762;1,60;2357;3,30;9604;3,80;16125;10,50;35013;2,70;335695;2,50;KX 470 3150;; +22080;VENDREDI;07/10/2022;4;06/01/2023;50;27;45;26;42;12;1;-26-27-42-45-50-;-1-12-;0;0;0;0;1;758017,10;2;7;25308,70;6;28;1970,70;89;610;166,60;249;1304;82,40;270;1399;53,90;4329;21194;17,80;5118;26962;15,60;13379;69411;11,20;24033;114346;8,30;83471;419972;7,10;234395;1151977;4,10;0;0;1;2724,80;49;30,20;116;12,90;2119;1,70;2395;4,10;11450;4,10;18978;11,50;38255;3,20;386633;2,80;TA 666 0275;; +22079;MARDI;04/10/2022;3;03/01/2023;3;28;18;43;42;12;3;-3-18-28-42-43-;-3-12-;0;0;0;2;6;92436,50;0;8;16202,90;9;34;1187,40;124;563;132,10;231;1135;69,20;300;1400;39,40;3342;16195;17,00;5660;26024;11,80;13267;61054;9,30;17636;83444;8,30;81925;379440;5,70;192022;888552;3,90;0;0;6;340,10;57;19,50;132;8,50;1708;1,60;2684;2,70;8860;3,90;13928;11,70;38060;2,40;312267;2,60;JR 131 2171;; +22078;VENDREDI;30/09/2022;2;30/12/2022;2;16;11;1;26;3;12;-1-2-11-16-26-;-3-12-;0;0;0;0;4;179820,00;2;8;21013,40;6;35;1496,00;231;939;102,70;310;1410;72,30;636;2396;29,90;4334;20078;17,80;8751;37307;10,70;24245;98004;7,50;21727;104135;8,60;114092;506261;5,60;297915;1280166;3,50;0;1484,10;2;593,60;106;13,80;146;10,10;2200;1,60;3925;2,50;10943;4,20;17712;12,00;51658;2,30;408654;2,60;FY 915 3671;; +22077;MARDI;27/09/2022;1;27/12/2022;34;4;44;20;21;3;1;-4-20-21-34-44-;-1-3-;0;0;0;0;4;128443,80;0;3;40025,90;7;28;1335,70;84;493;139,70;218;1039;70,10;236;1145;44,60;3394;16185;15,80;4841;23499;12,10;11199;54838;9,60;19405;90109;7,10;77307;357150;5,60;171076;796179;4,10;0;0;4;484,20;32;33,60;86;11,90;1532;1,70;2205;3,20;8577;3,80;14526;10,60;34857;2,50;318114;2,40;TC 019 1387;; +22076;VENDREDI;23/09/2022;6;23/12/2022;22;35;48;15;14;8;3;-14-15-22-35-48-;-3-8-;0;1;193007524,00;1;8;144591,60;3;7;38621,00;12;78;1079,50;242;1223;126,80;655;3125;52,40;544;2570;44,80;9921;46012;12,50;12232;55468;11,50;25415;116597;10,20;55727;251145;5,70;187224;826670;5,50;390148;1738164;4,20;1;2387,90;5;318,30;92;25,40;252;9,30;4088;1,40;5122;3,00;22939;3,20;38907;8,80;78557;2,40;729345;2,30;HI 522 2752;; +22075;MARDI;20/09/2022;5;20/12/2022;21;11;32;48;23;12;3;-11-21-23-32-48-;-3-12-;0;0;0;1;7;122581,80;1;11;18231,40;6;41;1523,50;211;999;115,10;333;1771;68,60;500;2396;35,60;5297;25261;16,90;9278;43062;11,00;22136;103568;8,50;28087;130620;8,20;131738;605372;5,50;322377;1453014;3,70;1;1752,60;0;0;81;38,90;143;11,70;2394;1,80;3985;2,80;12592;4,30;21198;11,80;56371;2,50;483054;2,50;PM 917 4088;; +22074;VENDREDI;16/09/2022;4;16/12/2022;49;10;45;36;27;3;4;-10-27-36-45-49-;-3-4-;0;0;0;2;6;178373,60;1;7;35733,30;7;38;2050,20;266;1121;128,00;417;2009;75,50;525;2347;45,40;6748;31691;16,80;10918;50031;11,80;22315;105337;10,50;38047;177657;7,50;162896;733690;5,70;339750;1541747;4,40;0;0;1;3893,30;110;19,30;178;12,00;2816;1,80;4687;3,00;16018;4,10;27511;11,30;69160;2,50;682570;2,20;FD 829 8683;; +22073;MARDI;13/09/2022;3;13/12/2022;40;9;12;15;47;1;11;-9-12-15-40-47-;-1-11-;0;0;0;0;2;415209,70;3;9;21564,70;7;31;1950,00;163;725;153,50;335;1694;69,40;444;1813;45,60;5001;23218;17,80;7886;35047;13,10;21433;91240;9,40;27462;126596;8,20;121033;529536;6,10;323683;1375378;3,80;0;0;4;624,50;78;21,90;163;10,40;2221;1,90;3386;3,30;12386;4,30;20633;12,10;52197;2,70;450279;2,70;DP 921 0829;; +22072;VENDREDI;09/09/2022;2;09/12/2022;26;17;23;24;27;4;9;-17-23-24-26-27-;-4-9-;0;0;0;1;7;164877,80;3;11;24522,00;22;71;1183,30;427;1547;100,00;985;3846;42,50;879;3427;33,50;14080;57158;10,00;17574;65480;9,70;40954;147134;8,10;70526;282824;5,10;240334;935906;4,80;532970;2053887;3,50;1;2722,70;7;311,10;189;14,30;415;6,40;5918;1,10;7334;2,40;29349;2,80;42235;9,20;100812;2,10;745650;2,60;VD 907 1255;; +22071;MARDI;06/09/2022;1;06/12/2022;10;44;7;29;22;4;5;-7-10-22-29-44-;-4-5-;0;0;0;2;8;66901,20;3;8;15635,90;12;38;1025,30;218;916;78,30;345;1529;49,60;520;1868;28,50;4505;20167;13,20;9264;36811;8,00;18721;73191;7,50;21409;98756;6,70;107193;465267;4,50;222101;933795;3,60;1;1143,30;6;152,40;97;11,70;138;8,10;1929;1,40;4009;1,80;9479;3,70;14467;11,40;46940;1,90;323055;2,50;KB 963 1015;; +22070;VENDREDI;02/09/2022;10;02/12/2022;7;45;12;20;13;3;12;-7-12-13-20-45-;-3-12-;0;1;128491607,00;1;5;193666,80;3;17;13312,60;7;40;1762,20;301;1227;105,80;474;2083;65,90;736;2875;33,50;6434;29649;16,20;13322;55173;9,70;31916;129049;7,70;31445;150508;8,00;172867;747913;5,10;418831;1778737;3,40;0;0;4;859,40;125;15,10;203;9,20;2986;1,50;5782;2,10;14618;4,00;21112;13,00;74488;2,00;493086;2,70;VE 788 1169;; +22069;MARDI;30/08/2022;9;29/11/2022;6;10;4;15;19;1;4;-4-6-10-15-19-;-1-4-;0;0;0;3;6;117055,20;6;18;9119,20;16;44;1161,90;447;1507;62,40;568;2278;43,70;781;2787;25,10;6842;28293;12,30;12643;48513;8,00;26028;102550;7,00;29252;128023;6,80;137790;574500;4,80;301262;1287400;3,40;1;1419,20;6;189,20;183;7,50;232;6,00;2817;1,20;5171;1,80;12016;3,60;16996;12,00;57498;2,00;367543;2,70;RJ 754 0942;; +22068;VENDREDI;26/08/2022;8;25/11/2022;22;23;44;25;38;12;11;-22-23-25-38-44-;-11-12-;0;0;0;1;6;143631,30;2;9;22379,30;4;36;1742,60;157;761;151,80;329;1559;78,30;496;2228;38,50;4303;22218;19,30;7363;36246;13,20;21948;103764;8,50;23069;117951;9,10;109787;526132;6,40;329167;1503262;3,60;1;1715,60;2;686,20;60;28,10;164;10,20;2001;2,10;3339;3,30;10731;4,90;17566;14,00;49451;2,80;420158;2,90;DV 256 7501;; +22067;MARDI;23/08/2022;7;22/11/2022;48;39;26;19;31;2;3;-19-26-31-39-48-;-2-3-;0;0;0;0;0;0;1;5;152669,50;5;22;2047,30;154;688;120,50;241;1150;76,20;276;1299;47,40;3716;18375;16,70;6016;29118;11,80;12157;59930;10,60;22272;106314;7,20;96562;444562;5,40;188411;893378;4,40;0;0;3;749,90;71;17,60;120;10,30;1670;1,80;2607;3,10;9653;4,00;18065;9,90;41757;2,40;407461;2,20;OY 818 0342;; +20222066;VENDREDI;19/08/2022;;19/10/2022;9;2;19;38;13;2;6;-2-9-13-19-38-;-2-6-;0;0;0;3;8;95084;4;11;16161,9;8;58;954,7;267;1092;93.4;756;2913;37;456;2104;36;9262;38526;9.8;11992;48895;8,6;22555;97525;8;41005;176398;5.4;147978;638876;4,6;292426;1328725;3,6;1;1471.3;4;294.2;121;12.1;346;4,2;3762;0,9;5084;1,9;16992;2,7;23194;9,2;63696;1,9;419753;1.9;GZ 976 4317;; +22065;MARDI;16/08/2022;5;15/11/2022;10;25;14;39;32;7;8;-10-14-25-32-39-;-7-8-;0;0;0;0;1;553832,80;2;8;16179,90;8;32;1259,90;148;688;107,90;252;1283;61,10;265;1353;40,70;3870;18294;15,00;6132;29745;10,30;11352;56424;10,10;21368;98087;7,00;92457;432022;5,00;168527;825623;4,20;0;0;4;497,80;75;14,70;91;12,10;1666;1,60;2655;2,70;9266;3,70;15620;10,20;40601;2,20;367805;2,10;UR 407 6925;; +22064;VENDREDI;12/08/2022;4;11/11/2022;14;42;35;17;34;10;6;-14-17-34-35-42-;-6-10-;0;0;0;0;1;734594,70;1;5;34337,30;7;33;1620,40;159;779;126,40;360;1605;64,80;386;1913;38,20;4620;22802;16,00;7142;33419;12,20;16262;80193;9,40;24239;119960;7,60;99359;471237;6,10;234712;1146192;4,00;0;0;3;881,60;62;23,30;157;9,20;2189;1,60;3246;2,90;11127;4,10;19710;10,70;44655;2,60;424562;2,40;OT 421 0975;; +22063;MARDI;09/08/2022;3;08/11/2022;47;18;21;19;27;11;5;-18-19-21-27-47-;-5-11-;0;0;0;1;1;548278,90;2;5;25628,30;2;22;1814,20;126;553;132,90;269;1356;57,30;257;1267;43,10;3899;19501;14,00;5993;28461;10,70;14566;64345;8,80;19448;99093;6,90;90261;419668;5,10;214506;943205;3,60;0;0;0;0;62;50,10;118;9,40;1742;1,60;2712;2,70;9054;3,90;14058;11,70;40138;2,30;310112;2,60;BE 416 8336;; +22062;VENDREDI;05/08/2022;2;04/11/2022;29;3;44;33;35;10;8;-3-29-33-35-44-;-8-10-;0;0;0;1;3;232396,30;0;5;32588,90;11;43;1180,30;117;594;157,30;294;1343;73,50;302;1444;48,00;4189;19596;17,70;6366;29962;12,90;15893;72826;9,90;23751;106863;8,10;94569;449494;6,10;229610;1086210;4,00;1;1445,20;6;192,70;65;22,20;147;9,60;1970;1,80;2867;3,30;11330;3,90;19339;10,70;43041;2,70;408538;2,50;QG 617 5022;; +22061;MARDI;02/08/2022;1;01/11/2022;28;48;18;40;7;11;8;-7-18-28-40-48-;-8-11-;0;0;0;1;4;128508,60;0;3;40046,10;2;28;1336,40;151;624;110,40;253;1233;59,10;339;1374;37,20;3342;16037;15,90;5905;27347;10,40;13342;58837;9,00;16890;82240;7,80;78375;358935;5,60;190180;834701;3,90;1;1100,10;2;440,00;61;18,00;123;8,80;1626;1,60;2640;2,70;8228;4,10;13351;11,90;36388;2,40;300660;2,60;EZ 401 9100;; +22060;VENDREDI;29/07/2022;3;28/10/2022;38;48;43;23;3;8;3;-3-23-38-43-48-;-3-8-;0;1;41717625,00;1;3;247760,30;2;9;19301,80;12;52;1040,50;205;886;112,40;504;2530;41,60;373;1674;44,20;6845;32761;11,30;8009;39072;10,50;15437;73325;10,40;38177;173838;5,30;116167;528289;5,50;229756;1053160;4,40;0;0;6;478,50;87;17,70;207;7,60;2867;1,30;3520;2,90;16091;3,00;26184;8,80;51731;2,50;490155;2,30;TZ 884 3683;; +22059;MARDI;26/07/2022;2;25/10/2022;3;5;17;27;25;9;1;-3-5-17-25-27-;-1-9-;0;0;0;0;5;109395,70;6;16;7989,80;10;43;926,00;254;1018;72,00;515;2020;38,30;583;2530;21,50;5557;24340;11,10;8896;38350;7,90;21608;90657;6,20;25123;108805;6,20;105623;466657;4,60;253288;1093742;3,10;0;0;5;420,20;116;9,90;214;5,40;2320;1,20;3881;1,90;10611;3,40;16025;10,50;46238;2,00;304593;2,70;RB 934 8551;; +22058;VENDREDI;22/07/2022;1;21/10/2022;32;29;50;18;16;11;4;-16-18-29-32-50-;-4-11-;0;0;0;0;0;0;1;9;112283,50;4;32;1863,40;150;779;141,00;296;1719;67,50;362;1847;44,10;4514;25769;15,80;6758;36344;12,50;16317;84710;10,00;24179;133912;7,60;100636;532663;6,00;246727;1262564;4,10;0;0;1;2838,80;72;21,60;130;12,00;2081;1,80;3131;3,30;11510;4,20;19327;11,70;45787;2,80;438660;2,50;CB 290 6960;; +22057;MARDI;19/07/2022;11;18/10/2022;41;6;27;40;23;2;12;-6-23-27-40-41-;-2-12-;0;1;230000000,00;3;5;4413400,80;3;10;30174,50;12;67;1402,70;310;1276;135,60;614;2639;69,30;644;3133;41,00;8822;37647;17,00;13375;58277;12,30;32455;142338;9,30;47021;199892;8,00;201806;857757;5,90;487454;2091175;3,90;1;2744,40;4;548,80;146;18,70;270;9,90;3789;1,70;5644;3,10;20444;4,10;34194;11,40;85332;2,60;751459;2,60;CB 011 6703;; +22056;VENDREDI;15/07/2022;10;14/10/2022;38;17;29;9;39;10;7;-9-17-29-38-39-;-7-10-;0;0;0;2;14;1884992,90;2;14;25775,50;33;107;1050,40;555;2251;91,90;1222;4163;52,50;1146;4949;31,00;14629;54892;14,00;21266;86203;9,90;44554;191410;8,30;74465;282715;6,80;280850;1140157;5,30;594492;2538124;3,80;1;3308,10;12;220,50;211;15,40;505;6,40;6110;1,30;8524;2,50;31310;3,20;50967;9,30;116097;2,30;981315;2,40;NY 259 7166;; +22055;MARDI;12/07/2022;9;11/10/2022;50;18;25;24;14;11;6;-14-18-24-25-50-;-6-11-;0;0;0;1;5;4569745,70;1;10;31243,40;10;47;2070,50;302;1314;136,40;745;2889;65,50;755;2986;44,50;10812;43803;15,20;14845;60640;12,20;34972;143449;9,60;60630;237936;7,00;225559;914593;5,70;537751;2152649;3,90;1;2985,70;2;1194,20;127;23,10;315;9,40;4748;1,50;6243;3,10;25794;3,50;41912;10,20;94392;2,50;826941;2,50;NL 693 1158;; +22054;VENDREDI;08/07/2022;8;07/10/2022;28;46;43;35;31;7;4;-28-31-35-43-46-;-4-7-;0;0;0;3;9;1453438,00;6;19;19952,40;17;90;1311,90;442;1566;138,80;1006;3945;58,20;861;3424;47,10;14612;59724;13,50;16818;69278;13,00;34655;149123;11,20;86337;345994;5,80;249636;1051572;6,00;509009;2217981;4,60;1;3461,50;9;307,60;171;19,70;413;8,10;5760;1,40;7045;3,20;34127;3,10;65330;7,60;104050;2,60;1128939;2,10;PK 211 8941;; +22053;MARDI;05/07/2022;7;04/10/2022;7;25;48;45;10;3;6;-7-10-25-45-48-;-3-6-;0;0;0;3;8;147139,80;3;6;45852,00;9;61;1404,70;320;1318;119,70;754;3044;54,80;692;2911;40,20;11830;46292;12,60;14442;59701;10,90;31836;129049;9,40;63346;247828;5,90;209827;873761;5,30;447540;1869390;4,00;1;2426,90;7;277,30;131;18,10;291;8,20;4795;1,20;5998;2,60;25695;2,90;42468;8,10;86230;2,20;719656;2,40;BA 534 6535;; +22052;VENDREDI;01/07/2022;;30/09/2022;46;6;34;24;18;12;3;-6-18-24-34-46-;-3-12-;0;0;0;0;4;318058,60;7;18;16519,00;11;80;1157,60;328;1386;123,00;582;2460;73,30;810;3286;38,50;8765;36778;17,20;14003;59403;11,80;33774;140590;9,30;44765;192169;8,20;202097;841034;5,90;486170;1998110;4,00;0;0;5;976,70;128;20,80;250;10,70;3870;1,70;5892;3,00;19415;4,30;33081;11,70;84663;2,60;768821;2,50;FI 641 7829;; +20222051;MARDI ;28/06/2022;;28/08/2022;35;10;47;42;48;11;2;-10-35-42-47-48-;-2-11-;0;0;0;0;5;184886,3;1;6;36009,1;5;32;2102,9;184;816;151,9;374;1674;78,2;402;1737;53;6391;27324;16,8;7879;35380;14,5;18173;81596;11,7;37438;158205;7,3;124324;549223;6,6;292778;1272807;4,6;0;0;1;3401,3;70;26,9;162;11,6;2637;1,7;3338;3,7;15742;3,7;28046;9,8;52726;2,9;558162;2,4;NU 014 6678;; +20222050;VENDREDI;24/06/2022;;24/08/2022;50;44;17;10;28;12;8;-10-17-28-44-50-;-8-12-;0;0;0;1;2;540484,2;2;9;28071,1;12;55;1430,7;308;1264;114,6;539;2286;67;810;3134;34,3;7144;31353;17,1;13452;55816;10,7;32739;131618;8,4;37178;160498;8,4;178270;749465;5,6;453131;1841469;3,7;0;0;4;1020,6;131;17,3;216;10,5;3073;1,8;5570;2,7;16526;4,3;26269;12,6;74701;2,5;607971;2,7;PH 612 8966;; +20222049;MARDI ;21/06/2022;;21/08/2022;23;36;39;6;47;10;8;-6-23-36-39-47-;-8-10-;0;0;0;0;3;283193,5;2;8;24820,1;9;38;1627,5;207;919;123,9;361;1721;69,9;473;2078;40,7;5678;24873;17;7964;37137;12,7;19173;86514;10,1;31922;135871;7,8;120550;538912;6,2;286708;1270534;4,2;0;0;1;3106,8;75;23;162;10,6;2388;1,8;3377;3,4;13530;4;23734;10,6;50108;2,8;501302;2,5;SZ 459 8109;; +20222048;VENDREDI;17/06/2022;;17/08/2022;21;22;38;19;31;7;11;-19-21-22-31-38-;-7-11-;0;0;0;0;1;1165998;4;17;16030,1;12;64;1326,2;333;1257;124,3;752;3032;54,5;621;2568;45,2;10492;42883;13,5;14964;59242;10,9;32503;128636;9,3;54301;222710;6,5;222215;872854;5,2;476312;1904166;3,8;0;0;2;2295,4;136;18,7;338;7,5;4426;1,4;6035;2,8;23009;3,4;36626;10,1;91677;2,2;756169;2,4;UU 153 5701;; +20222047;MARDI ;14/06/2022;;14/08/2022;7;40;2;27;34;3;11;-2-7-27-34-40-;-3-11-;0;0;0;1;4;122154,3;3;8;14274,7;4;21;1693,8;96;499;131,3;235;1110;62,4;311;1176;41,3;3401;15940;15,2;4811;23068;11,7;11892;55124;9,1;17408;83711;7,3;72863;346130;5,5;174448;795376;3,9;0;0;1;1768,9;35;28;105;9,3;1560;1,5;2194;3;8050;3,8;13071;10,9;32717;2,4;279449;2,5;RC 341 4915;; +20222046;VENDREDI;10/06/2022;;10/08/2022;37;26;40;17;36;9;12;-17-26-36-37-40-;-9-12-;0;1;64382899;1;1;728597,5;1;4;42571,3;7;29;1828,9;121;646;151,2;221;1253;82,4;322;1776;40,8;3404;18083;20;5671;29222;13,8;13928;71030;10,6;18997;99015;9,2;86499;437591;6,5;224221;1088946;4,2;0;0;3;845,7;45;31,3;119;11,8;1601;2,2;2560;3,6;8941;4,9;15809;13;38633;3;402432;2,5;PM 456 8559;; +20222045;MARDI ;07/06/2022;;07/08/2022;17;6;31;25;16;2;6;-6-16-17-25-31-;-2-6-;0;0;0;0;5;106815,1;2;12;10401,8;12;47;827,2;208;878;81,5;467;2118;35,7;343;1575;33,7;5716;25851;10,2;7051;32464;9,1;13754;64639;8,5;27836;124247;5,3;93527;431683;4,8;185313;884753;3,8;0;0;4;454,7;90;11,2;192;5,2;2360;1;2908;2,3;11551;2,7;17013;8,6;39534;2,1;297390;2,4;AR 511 7829;; +20222044;VENDREDI;03/06/2022;;03/08/2022;29;12;48;28;34;9;11;-12-28-29-34-48-;-9-11-;0;0;0;0;2;347955,6;1;3;54215,3;4;23;2202,6;107;563;165,7;263;1322;74,6;330;1452;47,7;4055;19667;17,6;6372;30360;12,7;15989;71744;10;21267;106808;8,1;98825;459619;5,9;246993;1086312;4;0;0;1;2476,7;40;34,3;128;10,7;1855;1,8;2853;3,2;9814;4,4;15841;12,6;43328;2,6;381813;2,6;IN 604 0060;; +20222043;MARDI ;31/05/2022;;31/07/2022;42;13;10;49;3;3;9;-3-10-13-42-49-;-3-9-;0;0;0;2;4;128700,6;0;2;60158,9;19;52;720,6;147;690;100;437;2030;35,9;250;1179;43,4;6455;29855;8,5;6838;30324;9,4;12619;56635;9,4;28566;132089;4,8;92546;425458;4,7;179549;820737;3,9;1;991,3;6;132,1;69;14,3;181;5,4;2772;0,8;2982;2,2;12326;2,5;17255;8,3;40132;2;286017;2,5;TO 602 3361;; +20222042;VENDREDI;27/05/2022;;27/07/2022;15;34;28;48;23;3;5;-15-23-28-34-48-;-3-5-;0;0;0;1;4;160252,9;3;9;16646,1;7;41;1138,1;141;737;116,6;375;1955;46,4;293;1421;44,9;5924;29864;10,6;6581;33243;10,7;12845;63461;10,4;32792;159182;5;99879;490862;5,1;196838;945421;4,3;0;0;3;732,2;55;22,1;153;7,9;2288;1,3;2683;3;13217;2,9;22520;7,9;41970;2,3;384144;2,3;GV 654 9838;; +20222041;MARDI ;24/05/2022;;24/07/2022;27;13;10;36;37;10;2;-10-13-27-36-37-;-2-10-;1;1;50656220;1;1;537399,7;0;3;41866,3;6;34;1150,6;108;541;133,2;282;1277;59,6;278;1316;40,6;4322;18138;14,7;5697;25446;11,7;13455;60944;9,1;22007;93118;7,2;86746;375666;5,6;201973;898612;3,8;1;1084,8;4;216,9;48;22,6;120;9;1929;1,4;2461;2,9;9767;3,4;14642;10,8;37545;2,3;300472;2,6;TG 800 0822;; +20222040;VENDREDI;20/05/2022;;20/07/2022;32;23;8;22;50;3;9;-8-22-23-32-50-;-3-9-;0;0;0;1;3;235008,1;0;6;27462,6;8;51;1006,3;136;749;126,2;418;2042;48,9;294;1549;45,3;6846;32205;10,9;7110;35345;11;15895;76630;9,5;38516;170398;5,1;113750;530839;5,2;247170;1128168;3,9;0;0;2;1284,2;57;25;168;8,4;2819;1,2;3002;3,1;15940;2,8;27057;7,6;48228;2,4;431666;2,4;SN 615 5728;; +20222039;MARDI ;17/05/2022;;17/07/2022;49;28;30;5;48;5;11;-5-28-30-48-49-;-5-11-;0;0;0;1;2;257215,2;1;2;60115,4;1;19;1970,9;84;447;154,3;191;1070;68,1;207;924;55,4;3356;16535;15,4;4855;22610;12,6;10660;48320;11;17735;90264;7,1;75113;347142;5,8;171668;756950;4,3;1;1052,2;1;841,7;41;25,6;78;13,4;1522;1,7;2199;3,2;8083;4,1;13097;11,7;33579;2,5;290248;2,6;KE 358 1334;; +20222038;VENDREDI;13/05/2022;;13/07/2022;11;45;17;48;3;8;4;-3-11-17-45-48-;-4-8-;0;0;0;3;5;160032;2;8;23376,3;15;57;1021,9;217;766;140;657;2190;51,7;541;1846;43,1;9542;32898;12,1;11382;38410;11,5;25955;88536;9,3;50946;173927;5,7;168822;573450;5,5;386060;1298894;3,9;0;0;7;554;95;22,6;283;7,6;3946;1,3;4998;2,8;21686;3,1;34232;9,1;72952;2,4;620010;2,5;FY 242 0121;; +20222037;MARDI ;10/05/2022;;10/07/2022;28;3;29;25;27;4;9;-3-25-27-28-29-;-4-9-;0;1;215840341;4;6;211426,5;2;11;26953;23;71;1300,6;419;1534;110,8;948;3546;50,7;908;3287;38,4;14514;56158;11,2;17173;65036;10,8;39227;144748;9;78810;305303;5,2;252573;946218;5,2;572525;2100825;3,8;3;979,2;10;235;181;16,2;391;7,5;5703;1,2;7125;2,7;31021;2,9;46502;9,2;103586;2,3;818102;2,6;OL 151 7591;; +20222036;VENDREDI;06/05/2022;;06/07/2022;3;24;8;18;40;11;3;-3-8-18-24-40-;-3-11-;0;0;0;1;5;265989,5;2;16;19426,9;14;89;1087,8;394;1691;105,4;867;3394;55,5;1035;3801;34,8;12426;46528;14,2;19296;73930;9,9;44914;165872;8,2;60944;234573;7,1;259432;989228;5,3;595321;2237066;3,7;0;0;4;1352,6;160;18,7;353;8,5;5217;1,4;7995;2,5;25746;3,6;40812;10,7;108150;2,2;839414;2,6;UZ 615 4334;; +20222035;MARDI ;03/05/2022;;03/07/2022;26;48;47;8;20;8;3;-8-20-26-47-48-;-3-8-;0;0;0;3;4;238540,9;1;5;44600,7;16;47;1477,8;232;892;143,4;610;2459;55;428;1852;51,3;9465;38194;12,4;11128;43316;12,2;23085;90377;10,9;50611;208019;5,7;169634;664875;5,6;351079;1383542;4,3;2;1034,6;6;275,9;96;21,5;248;8,3;3799;1,3;4585;3;20344;3,2;33146;9,1;69144;2,4;632661;2,3;HZ 505 8065;; +20222034;VENDREDI;29/04/2022;;29/06/2022;37;36;10;20;11;7;3;-10-11-20-36-37-;-3-7-;0;0;0;1;1;1222554,5;1;9;31747,9;22;75;1186,6;262;1100;149;1059;4020;43,1;525;2513;48,4;18124;68076;8,9;14725;57714;11,7;29169;119581;10,5;99566;374753;4;233357;900439;5,3;456474;1835388;4,2;0;0;7;684,7;127;20,9;429;6,2;7082;0,9;6102;2,9;38268;2,1;58884;6,6;95776;2,2;829597;2,3;NO 190 7720;; +20222033;MARDI ;26/04/2022;;26/06/2022;25;4;28;34;45;11;8;-4-25-28-34-45-;-8-11-;0;0;0;0;3;280770,6;2;5;39372,4;5;36;1703,2;170;797;141,7;395;1693;70,5;442;1753;47,8;5843;25263;16,6;8746;36848;12,6;20379;84377;10,3;31760;136157;7,7;133408;560118;5,9;320243;1301549;4,1;0;0;2;1597,9;76;23,3;194;9,1;2480;1,7;3601;3,3;13307;4,2;22023;11,7;55470;2,6;500133;2,5;RB 806 6218;; +20222032;VENDREDI;22/04/2022;;22/06/2022;11;6;35;36;21;9;1;-6-11-21-35-36-;-1-9-;0;0;0;1;5;195342;0;9;25363,6;16;56;1269,6;194;992;132;570;2289;60,4;464;2257;43,1;8388;34473;14,1;10462;45706;11,8;24068;105901;9,5;45119;184360;6,6;158116;674804;5,7;366273;1559220;3,9;0;0;8;444,6;89;22,2;231;8,5;3317;1,4;4339;3;17267;3,6;28613;10;64513;2,5;557133;2,5;BP 152 3003;; +20222031;MARDI ;19/04/2022;;19/06/2022;28;46;2;17;12;10;6;-2-12-17-28-46-;-6-10-;0;0;0;0;2;324441;2;5;30330,8;5;45;1049,7;204;770;113;399;1615;56,9;496;1906;33,9;5293;21749;14,8;8615;33018;10,9;21210;83185;8;26231;110648;7,3;114537;455485;5,6;278743;1142793;3,6;0;0;4;580,9;80;16,1;167;7,7;2218;1,4;3505;2,4;10743;3,7;17149;10,9;46933;2,2;356474;2,6;OX 396 6026;; +20222030;VENDREDI;15/04/2022;;15/06/2022;32;48;6;24;30;6;5;-6-24-30-32-48-;-5-6-;0;0;0;0;1;781552,4;2;8;22832,7;9;45;1264,3;215;928;112,9;375;1908;58;402;1754;44,3;5426;26410;14,7;8643;39470;11;16743;77433;10,4;29311;140945;6,9;126797;572191;5,3;244079;1122181;4,4;0;0;4;682;73;20,7;167;9;2290;1,6;3586;2,8;12058;3,9;19556;11,3;51977;2,4;480069;2,2;IL 682 7870;; +20222029;MARDI ;12/04/2022;;12/06/2022;30;47;35;31;21;10;2;-21-30-31-35-47-;-2-10-;0;0;0;0;1;596185,7;1;4;34834,6;5;19;2284,2;71;470;170,1;221;1116;75,7;206;1075;55,2;3532;17129;17,3;4277;23046;14,3;10673;55100;11,1;20771;96434;7,7;73209;364947;6,4;174803;873641;4,3;0;0;2;995,7;27;40,9;102;10,8;1527;1,8;1850;4;8817;3,9;16308;9,9;31256;2,9;331597;2,4;QA 116 8543;; +20222028;VENDREDI;08/04/2022;;08/06/2022;16;38;1;45;15;4;11;-1-15-16-38-45-;-4-11-;0;0;0;0;0;0;2;5;186499,3;7;34;1618,3;143;610;166,1;275;1447;74;306;1456;51,7;4064;21524;17,4;5954;29759;14,1;15006;72027;10,8;23247;119901;7,8;96909;471896;6,3;244301;1146720;4,1;0;0;2;1277,3;71;19,9;134;10,5;1851;1,9;2614;3,6;10264;4,3;17665;11,7;41146;2,8;394442;2,6;HW 474 9862;; +20222027;MARDI ;05/04/2022;;05/06/2022;13;1;24;11;49;5;6;-1-11-13-24-49-;-5-6-;0;0;0;0;4;138494,1;2;8;16184,1;8;42;960,1;217;825;90;321;1567;50,1;403;1651;33,4;4119;20735;13,3;7898;34174;9;15853;69799;8,2;19613;101715;6,8;100273;461263;4,7;200811;926680;3,7;0;0;4;453,4;86;11,7;127;7,9;1660;1,5;3263;2;8216;3,8;12377;11,8;41800;1,9;302012;2,4;SN 422 9124;; +20222026;VENDREDI;01/04/2022;;01/06/2022;21;45;31;2;7;10;4;-2-7-21-31-45-;-4-10-;0;0;0;0;2;366112,2;2;9;19014,7;6;37;1440,6;184;810;121,2;364;1607;64,5;434;2085;34,9;5429;22986;15,8;7831;35148;11,5;20862;91275;8,2;27753;118816;7,7;113649;503421;5,7;291351;1277295;3,6;0;0;2;1314,1;82;17,8;153;9,5;2365;1,5;3318;2,9;11926;3,8;19500;10,9;47926;2,5;397897;2,6;GN 645 0195;; +20222025;MARDI ;29/03/2022;;29/05/2022;43;33;11;8;42;11;6;-8-11-33-42-43-;-6-11-;0;0;0;0;5;101294,9;2;7;16910,2;4;17;2168,8;129;515;131,8;243;1021;70,3;281;1190;42,3;3590;16431;15,3;4596;22684;12,4;10677;53313;9,8;17530;86320;7,3;69210;338438;5,9;166486;807430;3,9;0;0;2;833,5;40;23,1;101;9,1;1564;1,4;2002;3;7816;3,7;12313;10,9;29780;2,5;253594;2,6;PH 399 1406;; +20222024;VENDREDI;25/03/2022;;25/05/2022;10;29;24;6;27;12;5;-6-10-24-27-29-;-5-12-;0;0;0;1;1;710352,4;6;15;11068;7;30;1723,7;236;1033;92,2;274;1519;66,2;585;2571;27,5;3994;21028;16,8;8765;40218;9,8;22830;99544;7,3;19289;105464;8,4;108137;529199;5,2;284718;1295523;3,4;0;0;2;1120,4;107;11,6;130;9,5;1806;1,7;3591;2,3;8815;4,4;13808;13,1;45343;2,2;324213;2,7;AR 411 2683;; +20222023;MARDI ;22/03/2022;;22/05/2022;7;29;17;40;21;8;11;-7-17-21-29-40-;-8-11-;0;0;0;1;3;154557,3;2;12;9030,6;4;22;1534,2;123;629;98,8;211;1078;60,9;327;1634;28,2;2876;14982;15,4;5613;26797;9,6;14106;66812;7,1;13963;72714;7,9;71919;352656;5,1;184901;870083;3,3;1;873,4;0;0;59;26,6;81;10,7;1267;1,7;2444;2,3;6263;4,3;9692;13,1;30732;2,3;233162;2,7;JF 838 0760;; +20222022;VENDREDI;18/03/2022;;18/05/2022;43;1;9;14;17;12;6;-1-9-14-17-43-;-6-12-;1;2;26825597;0;2;355494,7;0;4;41542,4;7;41;1262,3;169;706;135;339;1475;68,3;399;1877;37,7;4678;20381;17,3;7517;33136;11,9;20886;89281;8,2;23431;106380;8,3;105062;473322;5,9;283275;1262098;3,5;0;0;3;793,9;71;18,6;163;8,1;2108;1,5;3176;2,7;10729;3,8;17172;11,2;44570;2,4;348239;2,7;HI 537 1808;; +20222021;MARDI ;15/03/2022;;15/05/2022;2;26;19;12;1;8;2;-1-2-12-19-26-;-2-8-;0;0;0;0;0;0;4;8;79899,7;24;87;433,5;288;1051;66,1;679;2705;27,1;474;1858;27,7;7213;30929;8,3;9387;36928;7,7;17349;72137;7,4;29691;138426;4,6;102448;442888;4,6;205463;907842;3,6;0;0;11;157,9;124;7,7;269;3,5;2953;0,8;3877;1,6;12444;2,4;17135;8,2;42848;1,8;268993;2,6;JM 780 6574;; +20222020;VENDREDI;11/03/2022;;11/05/2022;23;22;12;19;48;6;2;-12-19-22-23-48-;-2-6-;0;0;0;1;1;684094,8;3;10;15988,4;10;50;996;224;822;111,6;621;2509;38,6;364;1741;39,1;8247;34390;9,9;9315;39716;9,5;18808;82995;8,5;37214;161325;5,3;131541;571093;4,7;262439;1180456;3,6;1;1304,7;3;347,9;91;14,3;240;5,4;3197;1;3846;2,2;14853;2,7;21198;8,9;54384;1,9;373673;2,5;HZ 865 0175;; +20222019;MARDI ;08/03/2022;;08/05/2022;18;12;31;25;21;9;2;-12-18-21-25-31-;-2-9-;0;0;0;1;4;122344,2;6;16;7148,4;18;46;774,4;190;825;79,5;389;1651;42;405;1715;28,4;4857;21459;11,3;7365;31873;8,5;15252;65731;7,7;22675;104736;5,8;91184;409157;4,7;187210;848061;3,6;0;0;6;280,8;70;13,3;173;5,4;1979;1,1;2983;2,1;9514;3;14391;9,4;38253;2;273823;2,4;UV 964 9515;; +20222018;VENDREDI;04/03/2022;;04/05/2022;40;20;43;6;42;10;12;-6-20-40-42-43-;-10-12-;0;1;78587957;1;2;394609,6;2;6;30742,2;3;29;1981,1;106;605;174,9;228;1220;91,7;316;1487;52,8;3896;19512;20,1;5531;28323;15,4;15272;74154;11;21057;105583;9,3;87334;426633;7,3;253773;1184843;4,2;1;1489,3;1;1191,4;43;34,6;106;14;1754;2,1;2536;3,9;9554;4,9;15785;13,7;39059;3,1;376593;2,8;SN 586 7936;; +20222017;MARDI ;01/03/2022;;01/05/2022;26;24;42;39;8;5;3;-8-24-26-39-42-;-3-5-;0;0;0;2;5;112506,9;1;11;11952,1;6;46;890,2;134;723;104,3;330;1742;45,7;332;1497;37,4;5146;24968;11,2;6004;29894;10,4;12278;59918;9,7;26896;132283;5,3;85142;422388;5,2;174070;842682;4,2;1;1034,1;3;275,7;51;20,2;129;8;2103;1,2;2557;2,7;10700;3;18454;8,1;35238;2,4;321589;2,3;MC 274 5810;; +20222016;VENDREDI;25/02/2022;;27/04/2022;24;10;30;42;13;1;5;-10-13-24-30-42-;-1-5-;0;0;0;1;4;184664,5;2;6;28772,7;6;39;1378,7;242;1070;92,5;488;2060;50,8;505;2232;32,9;6432;28930;12,7;9403;43199;9,4;21278;93110;8,2;31024;141194;6,5;126669;582386;5;281585;1257206;3,7;1;1384,8;3;369,2;80;17,3;179;7,7;2523;1,3;3741;2,4;12285;3,5;18913;10,6;51696;2,2;386002;2,6;MP 933 7534;; +20222015;MARDI ;22/02/2022;;24/04/2022;26;32;14;5;9;7;3;-5-9-14-26-32-;-3-7-;0;0;0;0;3;183996,6;1;3;43003;15;77;521,8;187;799;92,6;603;2694;29;326;1490;36,9;7705;35158;7,8;7526;33685;9,1;14191;65678;8,6;38485;174133;3,9;101375;457147;4,7;188738;889711;3,9;0;0;9;205,5;84;12,2;242;4,2;3075;0,8;3061;2,2;15468;2;24247;6,1;41962;2;318972;2,3;KH 634 1248;; +20222014;VENDREDI;18/02/2022;;20/04/2022;25;41;50;43;38;6;10;-25-38-41-43-50-;-6-10-;0;0;0;0;2;349382,3;1;5;32662,5;7;31;1640,9;115;533;175,8;239;1351;73,3;278;1396;49,8;3849;19585;17,7;5434;27049;14,3;12681;64231;11,2;22261;109089;8;83054;403027;6,8;192691;962264;4,6;0;0;1;2444,5;57;23,8;102;13,3;1707;1,9;2356;3,8;9846;4,3;18634;10,6;36020;3,1;414531;2,3;DI 121 0916;; +20222013;MARDI ;15/02/2022;;17/04/2022;33;20;31;9;44;12;2;-9-20-31-33-44-;-2-12-;0;0;0;1;2;248591,9;2;9;12911,1;2;22;1645,1;99;464;143,6;200;979;71,9;281;1394;35,5;2762;13358;18,5;4615;21957;12,5;11945;56956;9;14864;69371;8,9;67697;318971;6,1;174248;814913;3,8;0;0;1;1727,6;43;22,3;98;9,7;1313;1,8;2025;3,1;6973;4,3;11351;12,3;29786;2,6;263647;2,6;OO 039 8041;; +20222012;VENDREDI;11/02/2022;;13/04/2022;45;13;42;18;38;7;11;-13-18-38-42-45-;-7-11-;0;1;30928078;0;3;242348,5;2;7;24274,6;10;33;1603,8;165;746;130,6;360;1627;63,3;415;1720;42,1;5580;24950;14,5;7954;34601;11,6;16584;75151;10;29040;133275;6,8;117948;511434;5,6;248519;1115721;4,1;0;0;8;326,5;80;18,1;154;9,4;2459;1,4;3341;2,9;13005;3,5;21429;9,8;50893;2,3;445597;2,3;FY 859 0923;; +20222011;MARDI ;08/02/2022;;10/04/2022;43;34;20;6;37;7;10;-6-20-34-37-43-;-7-10-;0;0;0;0;3;172300,8;0;1;120808,6;6;19;1980,4;106;495;140;244;1002;73,1;214;1039;49,5;3627;14890;17,2;4559;20518;13,9;9813;46707;11,4;20984;86129;7,5;71159;322187;6,3;152556;718892;4,5;0;0;0;0;47;60,7;106;9,6;1669;1,5;1961;3,4;9155;3,5;16341;9,1;31176;2,7;324878;2,2;UB 190 2954;; +20222010;VENDREDI;04/02/2022;;06/04/2022;49;38;3;25;43;7;3;-3-25-38-43-49-;-3-7-;0;1;130000000;2;9;151235,5;3;11;28919,6;22;83;1193,7;326;1325;137,7;1113;4334;44,5;698;2796;48,4;18252;68027;9,9;15998;63476;11,9;31710;128892;10,9;102684;378974;4,4;248804;951638;5,6;482677;1915122;4,5;0;0;5;1113,1;138;22,4;447;6,9;7312;1;6716;3;40576;2,4;67468;6,6;102555;2,4;1007100;2,2;JT 631 4310;; +20222009;MARDI ;01/02/2022;;03/04/2022;49;36;1;19;38;9;6;-1-19-36-38-49-;-6-9-;0;0;0;1;2;301800,6;0;2;70535,7;5;29;1515,1;102;524;154,4;281;1442;59,3;228;1195;50,3;4543;21962;13,6;5389;25874;12,9;11269;55604;11,2;25495;124262;6;83612;400084;5,9;178784;861844;4,4;1;1119,2;4;223,8;47;23,8;128;8,7;1983;1,4;2312;3,2;10708;3,2;18782;8,7;35680;2,5;345114;2,3;NV 546 2186;; +20222008;VENDREDI;28/01/2022;;30/03/2022;34;10;25;29;45;10;9;-10-25-29-34-45-;-9-10-;0;0;0;0;2;385053,2;4;8;22498,3;5;26;2156,2;139;728;141,8;251;1332;81,9;328;1664;46,1;4106;20463;18,7;6918;34148;12,5;16026;77190;10,3;21569;107585;8,9;109826;514161;5,9;256248;1184365;4,1;0;0;2;1324,2;60;24,5;119;12,3;1749;2,1;2903;3,3;9521;4,8;15656;13,7;46378;2,6;418687;2,5;OO 207 7237;; +20222007;MARDI ;25/01/2022;;27/03/2022;16;6;47;18;39;2;4;-6-16-18-39-47-;-2-4-;0;0;0;0;2;272591,2;1;5;25483,6;2;20;1984,3;103;521;140,3;222;1197;64,5;221;1193;45,5;3936;18475;14,6;5195;24959;12,1;11153;53056;10,6;22542;103012;6,6;82772;379068;5,6;171949;806852;4,2;0;0;2;947;51;20,6;97;10,8;1656;1,5;2242;3,1;9269;3,5;15913;9,6;35025;2,4;326278;2,3;IN 364 8676;; +20222006;VENDREDI;21/01/2022;;23/03/2022;5;35;39;14;36;12;7;-5-14-35-36-39-;-7-12-;0;0;0;0;3;455155,7;4;14;22795,1;14;59;1684,7;332;1208;151,5;773;2442;79,2;820;2897;46,9;11700;38943;17,4;16247;56740;13,3;37132;131061;10,7;65809;215662;7,9;259974;879290;6,1;585053;2027241;4,2;0;0;3;2038,4;151;22,4;304;11,1;4788;1,7;6532;3,4;27218;3,9;47325;10,4;104960;2,6;1042335;2,3;PB 999 6610;F NWH 71157,F THD 16573,F HWD 28167,F VHM 94680,F TRH 67340,F KBX 42574,F HNS 76765,F CPV 01912,F PBP 12550,F JRZ 35853,F STP 64314,F QTJ 67362,F PXH 79858,F QMX 42431,F FWV 25413,F BJB 32849,F TLZ 26610,F CCZ 64000,F SHF 31374,F NJK 79627,F JTV 00190,F GNQ 50037,F JJD 37158,F NCN 99152,F GVZ 67432,F CBM 92592,F JJV 71787,F SDV 60006,E BBP 27831,E BDD 99803,E BBB 51860,E BCG 54031,E BFB 27852,E BDB 48546,E BFW 57864,E BBF 26358,E BDK 36121,E BFD 53629,E BFD 88478,E BBD 70418,E BBJ 34796,E BFR 73408,E BGD 84310,E BDB 62398,E BCH 27984,E BFZ 49430,Z TRM 14877,T VSG 00783,M VRZ 26827,X VSG 84995,V TRL 01585,Z TRR 94586,V TRL 87162,Z VSM 26552,J VSM 73525,T VSG 76136,J TRT 58673,V VRZ 30685,J VRZ 11907,H VSD 27040,H VSF 61310,H VSP 90761,T VSH 82930,Z QQJ 90798,X VSL 70214,H VSM 17788,M TRT 79385,T VSD 23735,T VRZ 55779,H VSK 04713,B HNT 35440,B HNV 30470,B HNQ 96583,B HMR 32763,B HPV 32773,B HNJ 64417,B HNK 86655,B HNL 54610,I MDZ 91577,I MFF 48547,I MFC 21006,P FPW 21873,P FLS 32079,P FCK 17673,P FLS 22482,P FFW 22456,P FFM 27813,P FFN 07949,P FKK 29281,P FMB 24858,P FDB 27796,R LSH 43275,R LSN 57742,R LSM 59586,R LSP 76359,S BBT 76008,S BBV 14622,N QCC 05383,W JQH 00203,O QBN 39384,; +20222005;MARDI ;18/01/2022;;20/03/2022;12;30;24;3;19;5;2;-3-12-19-24-30-;-2-5-;1;1;67441797;0;7;86794,8;6;13;10922,9;16;80;552,8;321;1255;64,9;570;2755;31,2;623;2525;23,9;6447;30831;9,8;10545;45913;7,3;21068;90734;6,9;28815;141146;5,3;116281;541879;4,4;237314;1081853;3,5;0;0;8;243,2;122;8,8;210;5,1;2571;1;4064;1,7;11254;3;16670;9,4;46894;1,9;311578;2,5;LN 606 2999;; +20222004;VENDREDI;14/01/2022;;16/03/2022;25;6;46;17;31;12;9;-6-17-25-31-46-;-9-12-;0;0;0;1;7;108903,2;2;11;16197;9;33;1681,6;168;770;132,7;239;1386;77,9;425;2062;36,8;3942;20404;18,6;7265;35096;12;18736;88805;8,8;20299;105704;9;100255;495606;6;265620;1256717;3,8;1;1377,3;4;275,4;67;20,5;112;12,2;1756;1,9;2943;3,1;9211;4,7;15197;13,2;42150;2,6;369624;2,7;GK 405 4997;; +20222003;MARDI ;11/01/2022;;13/03/2022;33;23;44;32;4;12;9;-4-23-32-33-44-;-9-12-;0;0;0;1;4;140479,6;1;7;18761,3;3;24;1704,4;94;601;125,3;168;1011;78,7;288;1613;34,7;2578;14452;19,3;4955;27126;11,5;13402;71603;8,1;14105;76170;9,2;69971;370274;5,9;187749;949409;3,7;0;0;0;0;40;70,9;77;13,1;1174;2,1;2142;3,1;6370;5;11125;13,2;30011;2,7;276907;2,6;FO 166 2052;; +20222002;VENDREDI;07/01/2022;;09/03/2022;14;2;50;37;44;7;11;-2-14-37-44-50-;-7-11-;0;0;0;1;4;175313,9;0;3;54631,6;6;35;1458,5;153;697;134,9;332;1521;65,3;323;1496;46,6;4518;22200;15,7;6654;30889;12,6;14463;71317;10,1;25900;123601;7,1;98821;457480;6;213720;1021956;4,3;1;1334,6;0;0;57;42,1;147;9;1983;1,6;2808;3,1;11147;3,7;19769;9,8;41683;2,6;419221;2,3;PL 507 0535;; +20222001;MARDI ;04/01/2022;;06/03/2022;28;41;18;35;36;6;11;-18-28-35-36-41-;-6-11-;0;0;0;0;0;0;0;4;153095,5;6;15;2408,9;98;441;150,9;170;914;76,9;250;986;50,1;2969;14142;17,4;3916;19243;14,3;9334;44666;11,4;16306;78161;7,9;59817;294292;6,6;143080;700245;4,5;0;0;1;1667,7;41;22,5;83;11,1;1325;1,7;1724;3,6;7155;4;13017;10,3;25394;3;271258;2,4;LN 456 9467;; +20211105;VENDREDI;31/12/2021;;02/03/2022;25;7;43;22;49;7;6;-7-22-25-43-49-;-6-7-;1;1;17571602;5;13;70556,7;0;14;15312,3;14;63;1059,8;238;1422;86,4;522;2488;52,2;411;2843;32,1;7132;33903;13,4;11128;52251;9,7;18524;97175;9,7;37113;175632;6,5;158994;729226;4,9;272706;1384192;4,2;2;807,2;6;215,2;92;17,5;223;7,2;2934;1,3;4594;2,3;15276;3,3;23584;9,9;65095;2;524611;2,2;JA 583 3740;; +20211104;MARDI ;28/12/2021;;27/02/2022;4;29;15;17;6;9;12;-4-6-15-17-29-;-9-12-;1;1;79411191;1;5;123889,4;3;20;7238,7;7;29;1554,9;182;855;97,1;253;1266;69,3;571;2372;26;3252;17231;17,9;7278;33956;10,1;20102;90446;7;16501;87730;8,8;91443;447127;5,4;247858;1159648;3,3;0;0;5;385,3;66;16,2;109;9,8;1453;1,8;2949;2,4;7011;4,8;11642;13,4;37984;2,3;280160;2,7;OU 447 1179;; +20211103;VENDREDI;24/12/2021;;23/02/2022;41;2;34;26;28;10;5;-2-26-28-34-41-;-5-10-;0;0;0;0;2;437555,9;1;7;29218,2;6;32;1990,7;152;722;162,5;337;1653;75;350;1652;52,7;5518;27096;16;7675;35790;13,5;17823;82458;10,9;31416;151724;7,2;121498;560096;6,1;285387;1282198;4,3;0;0;2;1497,5;60;27,7;145;11,4;2298;1,8;3134;3,5;13080;4;22650;10,7;49530;2,7;483044;2,5;MP 004 6365;; +20211102;MARDI ;21/12/2021;;20/02/2022;34;38;17;12;21;2;3;-12-17-21-34-38-;-2-3-;0;0;0;2;9;65400,7;1;12;11463,9;13;42;1020,2;199;931;84,7;331;1477;56,4;348;1810;32,3;4371;20595;14,2;8146;37634;8,6;14678;70096;8,6;21717;104598;7;107916;501404;4,6;199726;956524;3,9;1;1059,4;8;105,9;77;13,7;142;7,4;1829;1,4;3237;2,1;9136;3,6;13984;11;43405;2;327888;2,3;JG 205 1897;; +20211101;VENDREDI;17/12/2021;;16/02/2022;30;1;35;2;15;2;7;-1-2-15-30-35-;-2-7-;0;0;0;0;0;0;1;5;181673,9;12;48;1116,6;154;751;131,4;511;2196;47,5;252;1409;52;8505;34116;10,7;7593;35775;11,4;14388;70036;10,8;47987;186957;4,9;123156;547917;5,3;229778;1072072;4,3;0;0;5;505,9;64;21,9;218;6,4;3465;1;3162;2,9;19148;2,3;30133;6,8;50554;2,2;453640;2,2;RB 281 7994;; +20211100;MARDI ;14/12/2021;;13/02/2022;37;19;47;21;48;3;6;-19-21-37-47-48-;-3-6-;0;0;0;1;2;268761,2;1;5;25125,5;4;28;1397,4;119;535;134,7;299;1278;59,6;202;1122;47,7;4640;20117;13,3;4880;23986;12,4;10288;50934;10,9;27218;115410;5,8;77932;372695;5,6;161657;779628;4,3;0;0;2;924,6;48;21,4;116;8,8;1952;1,3;2051;3,3;11085;2,9;19923;7,5;33066;2,5;324962;2,2;BW 078 6554;; +20211099;VENDREDI;10/12/2021;;09/02/2022;11;21;42;38;33;8;2;-11-21-33-38-42-;-2-8-;0;0;0;0;1;702913,6;2;9;18253,6;11;51;1003,3;183;858;109,8;531;2416;41,2;388;1868;37,4;7916;34309;10,2;8054;36195;10,7;17411;78448;9,2;41368;177263;4,9;117705;511808;5,4;253415;1114785;4;0;0;6;431,3;75;19,1;206;6,9;3225;1,1;3344;2,8;16656;2,7;28720;7,3;48640;2,4;429739;2,4;GF 813 6344;; +20211098;MARDI ;07/12/2021;;06/02/2022;22;31;46;47;38;7;11;-22-31-38-46-47-;-7-11-;0;1;143469842;0;2;418527,2;5;8;24454,1;2;38;1603,5;163;744;150,8;340;1666;71,2;348;1596;52,2;5668;26098;15,9;7946;36010;12,9;16582;77524;11,1;31700;147002;7,1;117279;534527;6,1;249494;1156075;4,6;0;0;1;2871,2;60;26,5;147;10,8;2209;1,8;3227;3,3;12917;3,8;23821;9,7;47713;2,7;511775;2,2;LT 173 6325;; +20211097;VENDREDI;03/12/2021;;02/02/2022;46;21;32;22;29;9;10;-21-22-29-32-46-;-9-10-;0;0;0;1;6;197329,2;4;9;30746;8;45;1915,3;232;1029;154,2;476;2095;80,1;537;2401;49,1;7086;31282;18,8;11894;50869;12,9;27540;116931;10,4;38830;168029;8,8;185932;774721;6;438556;1816954;4,1;1;2498,9;3;666,3;102;24,4;198;12,6;3070;2;4990;3,3;16621;4,7;27933;13;76764;2,6;718197;2,5;CX 763 3302;; +20211096;MARDI ;30/11/2021;;30/01/2022;43;19;33;26;20;4;1;-19-20-26-33-43-;-1-4-;0;0;0;0;1;523711,8;0;3;40800;5;23;1657,5;105;493;142,4;212;1049;70,7;234;1105;47,2;3247;16002;16,3;4910;23029;12,6;11199;53637;10,1;18284;86639;7,5;74404;352475;5,8;172381;826573;4;0;0;1;1749,2;40;24,2;96;10,1;1346;1,8;2087;3,1;7620;4;12417;11,4;31426;2,5;277013;2,5;EY 114 1489;; +20211095;VENDREDI;26/11/2021;;26/01/2022;50;20;41;42;1;3;7;-1-20-41-42-50-;-3-7-;1;1;162289050;0;7;152675,6;1;7;35682,8;8;42;1852,3;194;927;154,6;489;2410;62,8;423;2039;52,2;9317;43207;12,3;8509;43094;13,7;18835;95975;11,5;62513;279019;4,7;142841;684207;6,1;295521;1461450;4,6;0;0;2;1693,9;66;28,5;188;10;3567;1,3;3452;3,6;23298;2,5;46179;5,9;57372;2,7;628123;2,1;JA 859 0214;; +20211094;MARDI ;23/11/2021;;23/01/2022;21;17;42;36;46;10;7;-17-21-36-42-46-;-7-10-;0;0;0;0;2;397120,9;1;8;23203,4;4;40;1445,4;158;831;128,1;352;1796;62,6;322;1819;43,4;5402;25664;15,4;7114;36160;12,2;15243;78528;10,4;31485;141863;7;108984;534870;5,8;231620;1170306;4,3;0;0;1;2476,4;71;19,3;149;9,2;2123;1,6;2725;3,3;12578;3,4;22791;8,8;42838;2,6;431208;2,3;NW 865 5429;; +20211093;VENDREDI;19/11/2021;;19/01/2022;50;26;47;22;38;6;2;-22-26-38-47-50-;-2-6-;0;0;0;2;2;493896,8;2;5;46172,7;6;49;1467,5;192;858;154,3;540;2424;57,7;426;2266;43,4;8227;36558;13,4;9209;43673;12,5;19565;95105;10,7;47478;205647;6;139493;644602;6;291946;1404040;4,4;0;0;3;1098,9;81;22,6;220;8,3;3150;1,4;3607;3,4;18113;3,1;32757;8,1;55544;2,7;590000;2,2;GX 143 6592;; +20211092;MARDI ;16/11/2021;;16/01/2022;9;32;36;19;21;4;3;-9-19-21-32-36-;-3-4-;0;0;0;1;4;180375,1;1;9;18736,2;6;31;1694,2;177;924;104,7;320;1727;59,2;365;1826;39,3;4650;24055;14,9;8031;40494;9,8;15948;80527;9,2;24812;125935;7,1;118470;572426;4,9;237147;1150170;3,9;0;0;2;1136,8;56;22,5;125;10,1;1892;1,6;3152;2,6;9664;4,1;15603;11,8;46525;2,2;385664;2,3;EW 964 8565;; +20211091;VENDREDI;12/11/2021;;12/01/2022;50;15;9;47;2;9;6;-2-9-15-47-50-;-6-9-;0;0;0;1;3;294755,3;4;10;20666,7;11;38;1693,9;173;879;134,9;540;2252;55,6;365;1786;49,3;7857;36380;12,1;9034;41559;11,8;18783;89086;10,2;41381;190793;5,8;138826;631438;5,5;290844;1347420;4,1;0;0;7;415,8;62;26;232;6,9;3080;1,3;3671;2,9;16169;3,1;25611;9,2;55437;2,4;487142;2,4;MC 718 7149;; +20211090;MARDI ;09/11/2021;;09/01/2022;33;25;36;26;17;8;1;-17-25-26-33-36-;-1-8-;0;0;0;0;1;651028,5;6;8;19019,5;7;23;2060,5;129;588;148,4;323;1431;64,4;322;1475;43,9;4359;20724;15,6;6182;29969;12;14800;72476;9,2;24586;112801;7,2;93406;444087;5,7;218716;1047359;3,9;0;0;3;724,6;60;20,1;110;10,9;1768;1,7;2479;3,2;9950;3,8;16943;10,4;38243;2,6;351578;2,4;IM 785 5464;; +20211089;VENDREDI;05/11/2021;;05/01/2022;41;25;2;12;30;11;8;-2-12-25-30-41-;-8-11-;0;0;0;0;5;161374,4;1;11;17143,5;6;30;1957,9;177;885;122,2;328;1654;69,1;441;2206;36,4;4786;23785;16,8;8278;38610;11,6;21046;94863;8,7;25611;126840;7,9;117189;548191;5,8;297917;1327463;3,8;0;0;2;1342,6;86;17,3;137;10,8;2029;1,8;3404;2,9;10904;4,3;18430;11,8;48065;2,5;414931;2,6;RP 633 2218;; +20211088;MARDI ;02/11/2021;;02/01/2022;29;43;2;3;17;4;1;-2-3-17-29-43-;-1-4-;0;0;0;1;2;286677,1;3;9;14889,1;3;23;1814,7;136;666;115,4;255;1306;62,2;317;1511;37,7;3726;18106;15,7;5886;28511;11,1;14057;67606;8,7;19726;94282;7,6;86781;412990;5,4;203390;969695;3,7;1;1034,5;1;827,6;51;20,2;98;10,5;1540;1,6;2501;2,7;8109;4;12870;11,7;35345;2,4;290679;2,5;KI 802 3707;; +20211087;VENDREDI;29/10/2021;;29/12/2021;28;10;32;15;49;9;3;-10-15-28-32-49-;-3-9-;0;0;0;1;5;155676,2;2;6;30320;13;54;1049,3;190;868;120,2;494;2259;48,8;363;1762;44;7503;33767;11,4;8388;38760;11,1;17205;80265;10;41479;187159;5,2;123200;567616;5,4;260428;1184940;4,1;1;1466,6;5;234,6;84;17,4;190;7,7;2918;1,2;3485;2,8;16777;2,7;27383;7,8;49956;2,4;439797;2,4;UF 931 7777;; +20211086;MARDI ;26/10/2021;;26/12/2021;12;44;42;15;20;2;1;-12-15-20-42-44-;-1-2-;0;0;0;0;3;181891,5;1;6;21255,5;8;23;1727,1;125;745;98,2;237;1115;69,3;273;1232;44,1;3351;16605;16,3;5781;26132;11,6;12487;58287;9,6;17671;87663;7,7;87589;394546;5,4;188884;884029;3,9;0;0;5;357,7;44;22,5;110;9;1400;1,7;2521;2,6;7316;4,2;11656;12,4;36301;2,2;288249;2,5;CP 970 9549;; +20211085;VENDREDI;22/10/2021;;22/12/2021;33;12;17;39;22;2;12;-12-17-22-33-39-;-2-12-;0;0;0;0;2;359277,2;1;12;13994,8;5;26;2011,8;147;865;111,3;341;1471;69,2;453;2286;31,3;4541;20875;17,1;7910;36139;11;20514;96218;7,7;22601;102778;8,7;112399;505361;5,6;285810;1286325;3,5;0;0;1;2442,9;52;26,1;150;9;2014;1,6;3243;2,8;10228;4,1;15198;13;47021;2,3;361595;2,7;QZ 850 6716;; +20211084;MARDI ;19/10/2021;;19/12/2021;40;4;36;41;20;1;6;-4-20-36-40-41-;-1-6-;0;0;0;0;4;135573,4;2;5;25348,5;10;27;1462,1;88;447;162,6;203;981;78,3;198;996;54,2;3362;15725;17,1;4149;20587;14,6;9620;47669;11,7;19826;90618;7,4;70045;327664;6,5;156319;750694;4,5;0;0;5;380;36;29,3;87;12,1;1377;1,9;1802;3,9;8123;4;15476;9,9;29494;2,9;325349;2,3;FL 466 0828;; +20211083;VENDREDI;15/10/2021;;15/12/2021;49;21;26;34;31;5;2;-21-26-31-34-49-;-2-5-;1;1;220000000;1;7;4608445,7;3;13;33931,7;21;97;1416,4;369;1630;155,2;993;4413;60,6;773;3579;52,5;15516;68202;13,7;18662;82993;12,6;39959;178081;10,9;89948;384869;6,1;296467;1290335;5,7;632118;2730160;4,3;1;3711,1;9;329,8;133;27,9;384;9,6;5828;1,5;7244;3,4;33656;3,4;60227;8,9;115108;2,6;1160332;2,3;UL 815 5842;; +20211082;MARDI ;12/10/2021;;12/12/2021;13;45;22;6;49;10;11;-6-13-22-45-49-;-10-11-;0;0;0;2;9;1315699,9;6;25;14703,3;21;78;1467,8;418;1567;134,5;680;3035;73,4;1066;3971;39,4;9797;44764;17,5;16821;70710;12,3;44422;178170;9,1;52240;237961;8,2;244587;1020988;6;639533;2584226;3,8;0;0;8;680,1;167;18,1;274;11;3845;1,9;6586;3;20761;4,5;33252;13,2;95765;2,6;806623;2,7;HI 479 6198;; +20211081;VENDREDI;08/10/2021;;08/12/2021;10;42;23;46;1;3;5;-1-10-23-42-46-;-3-5-;0;0;0;1;9;166984,9;7;23;15271,4;18;78;1402,6;360;1547;130,2;1026;4027;52,9;785;3194;46,8;15355;62333;12;17928;72563;11,5;38760;151970;10,2;84600;343913;5,4;271879;1094972;5,4;570294;2257847;4,2;0;0;8;670,1;158;18,8;375;7,9;5636;1,3;6753;2,9;30529;3;50978;8,5;102872;2,3;906709;2,3;BH 058 0251;; +20211080;MARDI ;05/10/2021;;05/12/2021;45;14;36;13;11;7;9;-11-13-14-36-45-;-7-9-;0;0;0;0;1;1033822,9;0;4;60405,3;20;61;1233,7;221;978;141,7;790;2802;52,3;472;2054;50,1;12128;44193;11,6;12392;50287;11,4;24393;100508;10,6;64809;235077;5,5;199504;783281;5,2;389964;1560164;4,2;0;0;7;539,2;94;22,3;309;6,7;4663;1,1;4758;2,9;24705;2,6;37571;8,1;76161;2,2;642513;2,3;NM 905 6314;; +20211079;VENDREDI;01/10/2021;;01/12/2021;1;19;15;48;26;6;12;-1-15-19-26-48-;-6-12-;0;0;0;2;7;174231,4;1;16;17815,3;19;54;1644,1;326;1267;129;653;2540;68;774;3003;40,4;9376;36320;16,7;13636;54580;12,4;34530;137241;9,1;48807;190019;8;199506;798724;6;504198;2008099;3,8;1;2417;8;241,7;122;19,8;276;8,7;3772;1,6;5447;2,9;19949;3,8;32964;10,7;78093;2,5;661919;2,6;JN 932 7034;; +20211078;MARDI ;28/09/2021;;28/12/2021;12;7;6;33;39;7;1;-6-7-12-33-39-;-1-7-;0;0;0;1;4;225472,9;1;1;210786,9;11;47;1396,9;232;964;125,4;666;2467;51,8;464;2137;42;10404;37666;11,9;12491;49107;10,2;24650;103133;9;51260;188980;5,9;185226;725287;4,9;370606;1518558;3,7;1;1727,6;4;345,5;83;20,8;255;6,7;3869;1,1;4792;2,4;19443;2,7;26263;9,6;70367;2;494709;2,5;NX 614 4402;; +20211077;VENDREDI;24/09/2021;;24/12/2021;27;12;20;2;41;10;12;-2-12-20-27-41-;-10-12-;0;0;0;1;5;259151,2;4;13;23295,3;7;51;1849,5;354;1327;130,9;573;2540;72,3;1088;3929;32,8;8529;36399;17,7;14623;58376;12,3;43486;165561;8;42739;187120;8,6;195539;804602;6,3;580154;2298981;3,5;1;2452,8;3;654;155;15,8;233;10,5;3512;1,7;5801;2,8;17260;4,4;27380;13;77399;2,6;615413;2,8;FB 984 4292;; +20211076;MARDI ;21/09/2021;;21/12/2021;20;25;26;30;38;8;2;-20-25-26-30-38-;-2-8-;0;0;0;1;2;274869,5;0;3;42827,6;5;30;1333,9;116;621;118,7;356;1692;46;231;1168;46,8;5024;24791;11;4915;26134;11,6;10537;56056;10,1;27838;136014;5;76441;388839;5,5;163015;842501;4,1;0;0;0;0;50;50,6;146;6,1;1939;1,1;2003;3;11005;2,5;18780;7;30636;2,4;272152;2,4;BS 245 9803;; +20211075;VENDREDI;17/09/2021;;17/12/2021;10;7;5;8;34;9;1;-5-7-8-10-34-;-1-9-;0;0;0;1;2;348150,6;4;13;12518,2;17;62;817,5;232;887;105,2;567;2384;41,4;486;2032;34,1;6865;29898;11,5;8970;40330;9,5;22221;96460;7,4;30640;141575;6,1;119531;551567;4,9;281249;1291010;3,4;1;1138,9;9;101,2;96;11,8;214;5,3;2411;1,1;3460;2,2;11189;3,2;15606;10,6;45991;2;300103;2,7;EG 935 7027;; +20211074;MARDI ;14/09/2021;;14/12/2021;12;38;45;18;35;2;8;-12-18-35-38-45-;-2-8-;0;0;0;0;3;164473,4;1;1;115320,4;10;24;1496,6;108;475;139,2;286;1384;50,5;184;1019;48,2;4343;21603;11,3;4441;22064;12,4;9254;47353;10,7;24622;119659;5,1;67621;334955;5,8;143515;719187;4,3;0;0;2;752,6;54;15,4;104;8;1720;1,2;1820;3;9828;2,6;17467;6,9;27222;2,5;253824;2,3;ST 166 4887;; +20211073;VENDREDI;10/09/2021;;10/12/2021;6;7;28;18;1;2;8;-1-6-7-18-28-;-2-8-;0;0;0;2;5;127306,6;4;16;9298;19;86;538,8;278;1028;83;712;2859;31,5;612;2343;27;8426;36393;8,7;9666;40988;8,6;22143;92523;7,1;35940;163738;4,8;119706;531205;4,7;260660;1163970;3,4;1;1076,4;7;123;112;9,6;255;4,2;3242;0,8;3737;1,9;13716;2,4;19565;8;46657;1,9;298352;2,6;SX 431 4676;; +20211072;MARDI ;07/09/2021;;07/12/2021;42;7;19;35;43;7;9;-7-19-35-42-43-;-7-9-;0;1;26572332;0;3;166558,5;2;7;16683,2;9;41;887,1;133;654;102,4;387;1719;41,2;242;1271;39,1;5384;24562;10,1;5398;27743;10;9636;50390;10,2;26789;121431;5,1;78982;396556;4,9;139722;727146;4,3;0;0;6;250,7;47;17,7;156;5,3;2154;0,9;2194;2,5;10440;2,5;15934;7,6;31863;2,1;265072;2,2;GK 745 4753;; +20211071;VENDREDI;03/09/2021;;03/12/2021;5;13;29;35;7;3;7;-5-7-13-29-35-;-3-7-;0;0;0;2;12;54980,5;2;7;22028,3;25;114;421,3;336;1349;65,5;1059;4933;18,9;472;2062;31,8;12380;57297;5,7;12011;52470;6,9;18561;86583;7,8;48893;228457;3,6;137477;630803;4,1;230477;1128625;3,7;2;546,9;6;145,8;114;9,5;408;2,6;4498;0,6;4543;1,6;18083;1,9;23121;6,9;53880;1,6;326328;2,4;KM 548 4193;; +20211070;MARDI ;31/08/2021;;30/11/2021;17;5;32;13;43;2;10;-5-13-17-32-43-;-2-10-;1;3;21214762;0;2;275986,4;3;12;10750,4;7;32;1255,6;141;720;102,8;263;1348;58;357;1859;29,5;3856;18459;14,8;5898;29147;10,5;14488;73331;7,7;18734;91842;7,5;78831;398312;5,4;191765;980564;3,5;0;0;1;1646,8;53;17,2;100;9,1;1628;1,4;2320;2,6;7906;3,6;12315;10,8;31615;2,3;251228;2,6;RK 713 6808;; +20211069;VENDREDI;27/08/2021;;26/11/2021;31;46;3;50;2;12;8;-2-3-31-46-50-;-8-12-;0;0;0;0;0;0;0;6;146580,9;11;29;1789,4;75;514;185,9;199;1212;83,3;207;1287;55,1;3039;18015;19,7;4551;26816;14,7;10811;64454;11,4;17307;98442;9;74628;420407;6,6;191865;1043071;4,3;0;0;3;693,1;37;31,2;81;14,2;1362;2,1;1955;3,9;7562;4,8;13113;12,8;31379;3;318783;2,6;BY 136 1121;; +20211068;MARDI ;24/08/2021;;23/11/2021;21;36;44;19;50;3;10;-19-21-36-44-50-;-3-10-;0;0;0;0;1;512584,1;0;4;29949,8;2;14;2665,3;90;513;133,9;196;1088;66,7;198;1101;46,3;2899;15634;16,3;4619;28240;10;9755;56666;9,3;16815;87604;7,3;60492;337024;6;141403;771144;4,2;0;0;1;1530,5;39;21,8;78;10,9;1266;1,6;1940;2,9;7162;3,7;12709;9,7;25357;2,7;249562;2,4;VU 587 4053;; +20211067;VENDREDI;20/08/2021;;19/11/2021;3;19;9;20;23;11;9;-3-9-19-20-23-;-9-11-;0;0;0;2;6;109566,6;1;12;12803,7;6;42;1139,4;328;1309;67,3;397;1844;50,5;672;2801;23,3;4283;23380;14;9975;46025;7,9;23939;106470;6,3;18670;106081;7,7;113383;554276;4,6;280585;1305852;3,1;0;0;3;650,7;133;8,1;160;6,7;1800;1,5;3900;1,8;7888;4,3;11517;13,7;45214;1,9;276227;2,8;VM 238 8638;; +20211066;MARDI ;17/08/2021;;16/11/2021;47;12;41;42;31;6;4;-12-31-41-42-47-;-4-6-;0;0;0;0;1;478210,5;1;6;18627,6;0;17;2047,7;59;396;161,9;213;1062;63,8;140;879;54,1;3686;16616;14,3;3500;19110;13,9;7493;40811;12,1;22087;97577;6,1;58108;298416;6,3;125272;655080;4,6;0;0;0;0;25;90,8;82;9,8;1436;1,4;1405;3,8;8762;2,9;15687;7,5;23514;2,8;251049;2,3;CX 053 7204;; +20211065;VENDREDI;13/08/2021;;12/11/2021;49;44;47;12;6;8;12;-6-12-44-47-49-;-8-12-;0;1;113156128;1;5;221900,2;0;3;86436,2;6;44;1835,6;228;935;159,1;466;2008;78,3;502;2148;51,4;7099;30340;18,2;10874;45411;13,5;26559;108054;10,6;37865;164079;8,4;169364;698112;6,2;419543;1697705;4,1;1;2073,1;3;552,8;98;21,1;177;11,7;2841;1,8;4158;3,3;14906;4,3;23917;12,6;64174;2,6;568129;2,6;OJ 202 7087;; +20211064;MARDI ;10/08/2021;;09/11/2021;47;9;48;49;37;2;7;-9-37-47-48-49-;-2-7-;0;0;0;0;0;0;0;5;159798,6;4;27;1746,1;98;541;160,5;291;1464;62,7;172;1159;55,6;5146;24538;13,1;5307;27053;13,2;10775;57882;11,5;33007;146686;5,5;82829;414997;6,1;164762;862282;4,7;0;0;2;948,8;43;24,5;120;8,7;1971;1,3;2016;3,5;12457;2,6;23871;6,4;31966;2,7;359503;2,1;CL 903 3732;; +20211063;VENDREDI;06/08/2021;;05/11/2021;32;21;7;26;14;12;4;-7-14-21-26-32-;-4-12-;0;0;0;0;8;99092;4;17;10898,5;6;34;1697,3;202;1156;91,9;346;1663;67,5;629;3552;22,2;4108;22313;17,6;8435;43955;10;23675;121748;6,7;20886;113718;8,7;109472;548208;5,7;298934;1458856;3,4;0;0;3;766,2;92;13,8;153;8,3;1701;1,8;3288;2,6;8661;4,6;13810;13,5;42226;2,4;330316;2,8;RJ 112 5381;; +20211062;MARDI ;03/08/2021;;02/11/2021;46;23;45;21;17;7;11;-17-21-23-45-46-;-7-11-;0;0;0;0;3;193278,1;1;7;19359,5;4;28;1507,5;110;570;136,4;260;1367;60,1;210;1262;45,7;3976;21882;13,1;5357;28177;11,4;10975;59138;10,1;20795;108734;6,6;83601;424487;5,3;176829;913172;4;0;0;1;1693,5;38;24,7;106;8,8;1571;1,4;2103;2,9;8246;3,5;13376;10,2;32459;2,3;284393;2,4;VF 519 7424;; +20211061;VENDREDI;30/07/2021;;29/10/2021;23;10;35;33;37;5;10;-10-23-33-35-37-;-5-10-;0;0;0;0;2;364049,2;0;10;17016,8;5;37;1432,5;149;829;117,7;281;1766;58,4;306;1737;41,7;4323;24831;14,6;6227;34600;11,6;14756;77234;9,7;23972;131983;6,9;94069;502696;5,7;218587;1133827;4;0;0;2;1048,7;53;21,9;112;10,4;1705;1,7;2458;3,1;9413;3,8;14920;11,4;36346;2,6;329328;2,5;QW 014 7486;; +20211060;MARDI ;27/07/2021;;26/10/2021;19;21;25;5;9;12;10;-5-9-19-21-25-;-10-12-;0;0;0;1;4;130748,8;5;22;5556;7;29;1312,8;172;701;100;229;1136;65,2;713;3077;16,9;2687;14194;18,3;5679;26730;10,8;22622;101650;5,3;12757;68940;9,5;68461;336665;6,1;244559;1154418;2,8;1;848,1;2;339,2;78;10,8;94;9;1125;1,8;2296;2,4;5297;5;8211;15;27890;2,5;192856;3,1;JA 020 0146;; +20211059;VENDREDI;23/07/2021;;22/10/2021;34;50;26;23;24;4;2;-23-24-26-34-50-;-2-4-;0;0;0;0;0;0;1;7;116054;5;32;1497,9;96;527;167,5;275;1459;63,9;210;1203;54,5;4489;22745;14,4;5542;29694;12,3;11215;62673;10,8;26140;128849;6,4;90208;469135;5,5;183307;982592;4,2;0;0;2;964,6;38;28,2;116;9,2;1733;1,5;2273;3,1;9819;3,4;16295;9,6;34500;2,5;333409;2,3;IH 676 1920;; +20211058;MARDI ;20/07/2021;;19/10/2021;19;48;3;41;12;6;9;-3-12-19-41-48-;-6-9-;0;0;0;1;4;120206,1;0;5;22475,3;5;28;1250;93;495;130,2;306;1445;47,1;234;1131;42,3;4262;20443;11,7;5092;24133;11;10649;51440;9,6;21348;106277;5,6;73592;357031;5,3;155305;760337;4;0;0;2;714,3;40;19,8;114;6,9;1746;1,1;2068;2,5;8206;3;12897;8,9;29203;2,2;235017;2,4;AM 956 8370;; +20211057;VENDREDI;16/07/2021;;15/10/2021;47;5;16;42;49;3;11;-5-16-42-47-49-;-3-11-;0;0;0;2;5;123838,4;0;4;36178,8;4;28;1609,8;109;544;152,6;253;1262;69,5;218;1144;53,9;3423;18008;17,1;4878;25421;13,5;11242;56981;11,2;19710;100104;7,7;74960;390232;6,2;180569;888347;4,4;1;1036,2;2;414,5;41;25,2;104;9,9;1432;1,8;1947;3,5;8357;3,9;14060;10,7;30007;2,8;304616;2,4;OF 263 5041;; +20211056;MARDI ;13/07/2021;;12/10/2021;22;11;6;8;5;3;4;-5-6-8-11-22-;-3-4-;0;1;26482986;1;5;99002,3;5;17;6805,4;12;67;537,8;350;1233;53,8;383;1676;41,8;699;2587;19;4032;19398;12,7;9525;38766;7;20139;81167;6,3;17286;87429;7;103746;458457;4,2;208484;924406;3,4;1;854,7;8;85,4;143;5,9;127;6,7;1572;1,3;3710;1,5;6758;3,9;10050;12,4;40371;1,7;245592;2,5;GX 336 1769;; +20211055;VENDREDI;09/07/2021;;08/10/2021;37;19;6;36;49;11;4;-6-19-36-37-49-;-4-11-;0;0;0;1;2;319379,4;1;5;29857,7;6;31;1499,9;107;577;148,4;236;1306;69,3;265;1318;48,2;3302;18096;17,5;5074;25590;13,8;12192;61276;10,7;18529;98577;8,1;78903;393624;6,4;199326;959979;4,2;1;1092,3;2;436,9;40;27,3;100;10,9;1392;1,9;2080;3,5;7889;4,3;13357;11,9;31680;2,8;305223;2,6;LQ 838 5498;; +20211054;MARDI ;06/07/2021;;05/10/2021;2;42;5;27;21;9;11;-2-5-21-27-42-;-9-11-;0;1;17000000;1;2;241620,4;0;6;18823,5;5;23;1529,4;121;584;110,9;195;1103;62,1;292;1417;33,9;2765;15104;15,9;5323;25950;10,3;13722;63521;7,8;13829;76100;7,9;71885;356761;5,3;185381;870053;3,5;1;829,5;3;221,2;39;21,2;71;11,6;1149;1,8;2155;2,5;5856;4,4;9333;12,9;29338;2,3;220007;2,7;JB 310 7646;; +20211053;VENDREDI;02/07/2021;;01/10/2021;49;29;38;9;14;7;10;-9-14-29-38-49-;-7-10-;0;1;77959528;1;2;423174,2;2;7;28257,9;16;41;1502,7;207;969;117,1;419;2020;59,3;368;2069;40,7;5521;27338;15,4;8264;42415;11;17393;94868;9,2;28903;145272;7,2;112590;590518;5,6;240729;1317318;4;0;0;6;372,6;94;13,2;147;8,4;2198;1,4;3205;2,5;11286;3,4;18555;9,7;43731;2,3;377189;2,3;OM 594 3615;; +20211052;MARDI ;29/06/2021;;28/09/2021;6;12;24;20;48;6;5;-6-12-20-24-48-;-5-6-;0;0;0;2;5;111254,2;1;5;26001,9;9;55;736,2;247;1139;65,4;350;1745;45,1;474;1863;29,7;4398;21751;12,7;8409;36620;8,4;14745;66073;8,7;19897;104567;6,6;101789;464731;4,7;189145;881402;4;0;0;2;821,6;95;9,6;132;6,9;1743;1,3;3273;1,8;7665;3,7;11196;11,9;39179;1,9;275061;2,4;BL 604 3203;; +20211051;VENDREDI;25/06/2021;;24/09/2021;33;29;36;1;30;12;3;-1-29-30-33-36-;-3-12-;0;0;0;0;0;0;1;7;126640,6;4;24;2179,5;89;523;184,2;193;1177;86,5;223;1203;59,5;3337;18343;19,5;5068;26737;14,9;12466;63393;11,7;18769;100417;8,9;84972;432899;6,5;210190;1049471;4,3;0;0;2;1065,8;45;26,3;85;13,9;1393;2,1;1989;3,9;7600;4,9;13115;13,1;33443;2,9;334315;2,5;JE 105 2161;; +20211050;MARDI ;22/06/2021;;21/09/2021;27;24;26;6;41;8;3;-6-24-26-27-41-;-3-8-;0;0;0;1;1;517837,3;1;3;40342,3;4;26;1449,8;105;537;129,3;278;1503;48,8;184;1098;46,9;4280;22412;11,5;5122;25742;11,1;10262;53604;9,9;23850;121431;5,3;77852;386296;5,2;159598;801213;4,1;0;0;2;768;43;19,8;115;7,4;1656;1,2;2055;2,7;8882;3;14488;8,5;30596;2,3;260100;2,3;JX 716 1550;; +20211049;VENDREDI;18/06/2021;;17/09/2021;27;20;41;11;30;3;5;-11-20-27-30-41-;-3-5-;0;0;0;0;7;96928,2;4;8;19822;12;61;809,7;193;916;99,3;550;2570;37,4;369;1808;37,3;7355;33949;9,9;8454;40030;9,4;16612;76478;9,1;35902;171005;4,9;118570;554561;4,8;228836;1061478;4;0;0;3;698,1;76;15,3;169;6,8;2690;1;3128;2,4;12726;2,8;20727;8,1;44837;2,1;354576;2,3;RV 533 2111;; +20211048;MARDI ;15/06/2021;;14/09/2021;30;31;11;26;21;2;3;-11-21-26-30-31-;-2-3-;0;0;0;0;2;241573,8;0;6;18819,9;6;32;1099,1;146;727;89,1;243;1243;55,1;285;1339;35,9;3182;16157;14,8;6488;30482;8,8;12158;58911;8,4;16304;83088;7,2;84541;400903;4,7;159921;771992;3,9;0;0;3;500,9;52;16;86;9,7;1277;1,6;2402;2,3;6382;4,1;11018;11;33126;2;264635;2,2;HW 514 9335;; +20211047;VENDREDI;11/06/2021;;10/09/2021;17;33;9;21;39;7;11;-9-17-21-33-39-;-7-11-;1;1;30382655;0;7;99796,3;8;17;9604;5;68;747,8;246;1160;80,7;422;2095;47,2;483;2449;28,4;5648;26487;13,1;9306;43854;8,8;19612;93506;7,7;27216;128929;6,7;121178;551033;5;254992;1189088;3,7;0;0;3;752,9;90;13,9;156;8;2226;1,4;3567;2,3;10916;3,6;16834;10,8;47118;2,1;369698;2,4;NO 049 7049;; +20211046;MARDI ;08/06/2021;;07/09/2021;47;26;2;36;11;9;7;-2-11-26-36-47-;-7-9-;0;0;0;0;3;169023,9;0;3;39503,6;6;29;1272,8;93;521;130,5;326;1302;55,2;216;1101;45,8;4646;20252;12,4;5294;24558;11,4;9896;48064;10,9;26711;113013;5,6;79038;367540;5,4;149904;721818;4,4;0;0;2;811,9;43;20,9;117;7,7;1820;1,2;2098;2,8;10324;2,7;17907;7,3;31370;2,3;295278;2,2;KE 956 9754;; +20211045;VENDREDI;04/06/2021;;03/09/2021;40;20;36;46;7;2;4;-7-20-36-40-46-;-2-4-;0;1;130000000;1;3;422567,4;3;15;19752,1;10;69;1337,4;278;1181;143,9;655;2623;68,5;560;2458;51,3;10247;41510;15,2;13146;54563;12,9;28419;118452;11;58215;232145;6,8;206525;843437;5,9;445125;1836279;4,3;0;0;5;905,4;117;21,4;246;10,2;3813;1,6;5076;3,3;21664;3,6;37432;9,8;78453;2,6;777029;2,3;GK 266 0524;; +20211044;MARDI ;01/06/2021;;31/08/2021;35;10;44;3;26;7;6;-3-10-26-35-44-;-6-7-;0;0;0;1;4;157524,1;1;14;10518,8;8;26;1764,1;169;772;109,4;280;1508;59,2;292;1527;41,1;4841;22450;13,9;7189;33487;10,4;13518;66919;9,7;26700;124879;6,3;105266;479419;5,1;195346;954097;4,1;0;0;4;476,7;60;17,6;119;8,9;1903;1,3;2673;2,6;9973;3,3;17161;9;39997;2,1;353193;2,1;OW 729 3397;; +20211043;VENDREDI;28/05/2021;;27/08/2021;16;40;13;11;34;1;12;-11-13-16-34-40-;-1-12-;0;0;0;1;4;198582,2;1;8;23205,9;4;23;2514,1;124;667;159,6;245;1460;77,1;333;1812;43,6;4202;21591;18,3;5806;31367;14;16709;89013;9,2;23138;119317;8,3;91443;476856;6,5;270008;1364346;3,7;0;0;3;761,4;46;27,5;106;11,9;1735;1,8;2297;3,7;9715;4,1;15723;11,7;35976;2,9;313345;2,9;JO 710 8313;; +20211042;MARDI ;25/05/2021;;24/08/2021;38;21;26;1;30;8;2;-1-21-26-30-38-;-2-8-;0;0;0;0;0;0;1;3;234077,1;6;28;1479,8;99;550;138,7;334;1669;48,3;240;1136;49,9;4983;25385;11,1;5303;27184;11,6;11340;57873;10,1;28045;139240;5,1;78644;403152;5,5;168006;872777;4,1;0;0;4;400;36;24,6;134;6,6;1933;1,1;2089;2,8;10780;2,5;18370;7;30435;2,4;267617;2,4;AN 097 3344;; +20211041;VENDREDI;21/05/2021;;20/08/2021;9;13;35;2;15;10;3;-2-9-13-15-35-;-3-10-;0;0;0;2;5;149387,9;2;5;34914,4;8;43;1264,5;195;842;118,9;407;1830;57,8;475;2005;37,1;5942;25929;14,3;8743;39484;10,5;21621;94556;8,1;29721;133141;7;122079;561023;5,2;289972;1318698;3,6;0;0;4;564,4;76;16,5;143;8,7;2285;1,3;3373;2,4;11740;3,3;17631;10,3;46658;2,2;342203;2,6;MJ 580 8475;; +20211040;MARDI ;18/05/2021;;17/08/2021;13;39;40;38;5;4;12;-5-13-38-39-40-;-4-12-;0;0;0;0;1;542038,1;0;2;63341,6;0;18;2192,1;81;460;158;165;900;85,3;209;1138;47,4;2550;14120;19,1;3947;21583;13,9;10559;55601;10;13977;75896;8,9;63578;330144;6,4;170170;869002;3,9;0;0;0;0;34;74,2;81;11,1;1084;2;1605;3,7;5899;4,8;9981;13,1;25689;2,8;241379;2,7;NG 483 3243;; +20211039;VENDREDI;14/05/2021;;13/08/2021;6;38;29;16;18;6;11;-6-16-18-29-38-;-6-11-;0;0;0;2;4;172797,8;2;7;23077,5;7;32;1572,3;158;800;115,8;362;1732;56,5;356;1830;37,6;4979;24464;14;7506;35541;10,8;17118;80947;8,8;23611;116601;7,4;101842;491563;5,5;236399;1134203;3,8;0;0;4;512,2;64;17,7;148;7,6;2077;1,3;2966;2,5;9800;3,6;15380;10,8;40080;2,3;308295;2,6;HJ 787 6281;; +20211038;MARDI ;11/05/2021;;10/08/2021;14;46;43;34;25;6;5;-14-25-34-43-46-;-5-6-;0;0;0;0;1;507955,6;2;5;23743,5;4;19;1946,1;110;498;136,7;187;998;72,1;183;1005;50,3;2719;15462;16,3;4401;22890;12,3;8617;46031;11,4;15789;88416;7,1;65159;338989;5,9;129681;674594;4,7;0;0;2;780,6;46;18,8;80;10,8;1091;1,9;1787;3,2;6281;4,3;12043;10,5;25956;2,7;292408;2,1;MY 017 0036;; +20211037;VENDREDI;07/05/2021;;06/08/2021;33;38;47;22;14;9;8;-14-22-33-38-47-;-8-9-;0;1;69463976;1;4;198903,4;3;8;23243,5;7;39;1485;204;1031;103,4;345;1687;66,8;403;2125;37,2;4397;23019;17,2;8258;41841;10,5;17713;85768;9,5;24115;126706;7,8;110303;567932;5,5;235301;1177496;4,2;1;1323,8;3;353;90;14,7;142;9,3;1734;1,9;3182;2,7;9773;4,2;17488;11;42461;2,5;420266;2,2;ON 789 2710;; +20211036;MARDI ;04/05/2021;;03/08/2021;46;13;10;28;3;11;4;-3-10-13-28-46-;-4-11-;0;0;0;0;3;188139,9;4;12;10992,8;7;32;1284;149;670;112,9;265;1366;58,5;411;1802;31,2;3426;18248;15,4;6266;29362;10,6;17079;76213;7,6;17026;91973;7,6;83782;404486;5,5;216940;1014527;3,5;0;0;1;1691,2;54;17,3;109;8,6;1415;1,6;2505;2,5;7000;4,2;11276;12,1;33329;2,3;248339;2,7;IP 493 9062;; +20211035;VENDREDI;30/04/2021;;30/07/2021;1;28;24;46;16;11;2;-1-16-24-28-46-;-2-11-;0;0;0;0;3;281487;0;10;19736,4;7;55;1117,7;130;794;142,6;323;1872;63,9;381;1936;43,4;4787;26918;15,6;6844;38059;12,3;17500;90986;9,6;25669;143162;7,3;100966;556634;5,9;251389;1322178;4;0;0;2;1145;51;24,9;139;9,1;1991;1,5;2741;3,1;10461;3,8;17771;10,4;40320;2,6;357309;2,5;JI 173 8621;; +20211034;MARDI ;27/04/2021;;27/07/2021;13;49;14;24;35;1;10;-13-14-24-35-49-;-1-10-;0;0;0;0;2;267062,2;0;1;124833,7;5;21;1851,5;92;490;146,1;201;977;77,5;199;1182;45;2839;14222;18,7;4266;22290;13,3;11211;58745;9,4;14952;75911;8,8;66263;330528;6,3;176669;883871;3,8;0;0;3;535,3;35;25,4;87;10,2;1160;1,9;1752;3,4;6220;4,5;10157;12,8;26729;2,7;233202;2,7;CS 269 2623;; +20211033;VENDREDI;23/04/2021;;23/07/2021;29;19;15;3;14;2;8;-3-14-15-19-29-;-2-8-;0;0;0;1;5;136455,9;1;9;17717,7;13;68;730,4;193;979;93,4;602;2697;35,8;476;2186;31;8394;37526;9;9340;41712;9;20638;93109;7,5;39724;181368;4,7;122045;555053;4,8;267762;1216697;3,5;0;0;3;707,7;67;17,6;221;5,3;3173;0,9;3600;2,1;15173;2,4;21916;7,8;46799;2;332652;2,5;KZ 186 1003;; +20211032;MARDI ;20/04/2021;;20/07/2021;28;46;17;5;41;11;10;-5-17-28-41-46-;-10-11-;0;1;68414615;0;3;200125;2;7;20045,3;3;28;1560,9;142;639;125,9;213;1141;74,5;324;1568;38,1;2911;15930;18,7;5265;26236;12,7;14599;69173;8,9;15123;83399;9;74427;369576;6,4;209637;1000036;3,8;0;0;2;912,4;53;19,1;84;12;1153;2,1;2060;3,2;6206;5,1;10065;14,7;29744;2,8;256490;2,8;ND 401 3611;; +20211031;VENDREDI;16/04/2021;;16/07/2021;29;11;40;48;6;9;5;-6-11-29-40-48-;-5-9-;0;0;0;0;5;154073,8;1;5;36009,5;9;51;1099,6;152;767;134,6;458;2212;49,3;354;1682;45,6;6515;32527;11,7;7591;36313;11,7;16126;75862;10,5;35469;180117;5,3;115809;551262;5,5;243000;1136433;4,3;0;0;5;472,9;49;26,8;162;8,1;2355;1,3;2989;2,9;12792;3,2;21849;8,7;44375;2,4;396656;2,4;KU 471 4103;; +20211030;MARDI ;13/04/2021;;13/07/2021;47;16;31;50;20;8;2;-16-20-31-47-50-;-2-8-;0;0;0;0;0;0;3;9;78273,2;5;31;1340,8;94;536;142,8;249;1318;61,4;190;1115;51;4506;22013;12,9;4385;24079;13,1;10037;52748;11,1;27641;133290;5,3;71854;364122;6,1;158880;804157;4,5;0;0;2;892,4;37;26,8;94;10,5;1787;1,3;1763;3,7;11011;2,8;20466;7;28833;2,8;312124;2,3;PG 009 8337;; +20211029;VENDREDI;09/04/2021;;09/07/2021;44;8;35;32;2;8;11;-2-8-32-35-44-;-8-11-;0;0;0;0;3;247364,3;7;13;13341,4;6;26;2077,7;165;777;128;324;1520;69,2;345;1662;44,4;4110;21426;17,2;6935;34882;11,8;15908;76962;9,9;21885;114624;8,1;100067;492560;5,9;233128;1114677;4,2;0;0;2;1168,7;60;21,6;148;8,7;1706;1,9;2783;3,1;9120;4,4;15160;12,5;40381;2,6;361123;2,6;QL 627 3053;; +20211028;MARDI ;06/04/2021;;06/07/2021;50;21;37;38;2;8;7;-2-21-37-38-50-;-7-8-;0;0;0;0;3;171378,8;1;2;60081;2;20;1871,3;72;421;163,7;189;982;74,2;193;952;53,7;2950;15459;16,5;4149;21900;13;8239;44909;11,8;18318;88631;7,2;70663;354897;5,7;134846;696508;4,6;0;0;0;0;29;87,8;72;12,6;1237;1,8;1689;3,6;7322;3,9;13222;10;28614;2,6;320982;2;EC 415 4458;; +20211027;VENDREDI;02/04/2021;;02/07/2021;21;47;4;40;34;2;5;-4-21-34-40-47-;-2-5-;0;1;144755907;0;1;1027964,6;2;8;30031,5;10;44;1700,7;189;1015;135,8;498;2498;58,3;441;2147;47,6;8470;38360;13,3;9800;45644;12,5;20963;95022;11,1;50788;220716;5,8;154107;694744;5,8;323808;1435512;4,5;0;0;3;1157,1;86;22,4;196;9,8;3003;1,6;3710;3,4;17990;3,3;33697;8,3;57970;2,7;619232;2,2;JU 837 6212;; +20211026;MARDI ;30/03/2021;;29/06/2021;42;24;29;28;27;8;4;-24-27-28-29-42-;-4-8-;0;0;0;0;4;200288,1;0;4;46810,6;11;39;1495,4;166;765;140,4;439;2156;52,6;331;1573;50,7;7026;33349;11,9;7832;37459;11,8;16685;81606;10,1;38448;182285;5,5;124370;586711;5,3;267355;1261780;4;0;0;4;593,3;64;20,6;153;8,6;2463;1,3;2921;3;13867;2,9;20614;9,3;46022;2,3;374426;2,5;RQ 559 8594;; +20211025;VENDREDI;26/03/2021;;25/06/2021;49;12;50;10;37;8;3;-10-12-37-49-50-;-3-8-;0;0;0;2;5;202737,6;2;21;11281,6;13;55;1341,6;216;1013;134,1;548;2595;55,3;488;2161;46,7;8988;40468;12,4;10644;47140;11,9;22682;100247;10,4;50479;225736;5,6;165613;716203;5,5;348445;1509225;4,2;0;0;5;657,3;77;23,7;220;8,3;3200;1,4;3978;3;18144;3,1;30697;8,6;61388;2,4;560282;2,3;LJ 741 9751;; +20211024;MARDI ;23/03/2021;;22/06/2021;2;26;29;36;49;3;9;-2-26-29-36-49-;-3-9-;0;0;0;0;3;236598;2;8;20736,3;8;33;1565,7;144;628;151,5;385;1756;57,3;275;1354;52,2;5960;28140;12,5;6271;30210;13;13123;65288;11,2;35215;161700;5,4;98323;473034;5,9;206974;990928;4,5;0;0;1;2156,6;54;22,1;120;9,9;2262;1,3;2369;3,3;13179;2,8;23475;7,4;37236;2,6;376263;2,3;GI 739 3104;; +20211023;VENDREDI;19/03/2021;;18/06/2021;35;44;43;9;3;10;1;-3-9-35-43-44-;-1-10-;0;0;0;1;4;220250,3;1;6;34317,4;5;26;2466,7;123;707;167,1;285;1527;81,7;362;1820;48,2;4690;22752;19,2;7017;33727;14,5;18358;88534;10,2;26421;124513;8,8;112463;530063;6,5;299348;1406445;3,9;0;0;1;2695,6;46;32,5;101;14,8;1923;1,9;2754;3,6;10531;4,4;17783;12,2;43945;2,8;399687;2,7;US 442 8833;; +20211022;MARDI ;16/03/2021;;15/06/2021;32;25;23;47;35;2;6;-23-25-32-35-47-;-2-6-;0;0;0;0;4;165026,6;0;6;25712,9;5;40;1201,3;133;628;140,9;333;1686;55,5;287;1362;48,2;5026;25162;13;5746;29856;12,2;12051;65078;10,4;29495;140955;5,8;87321;446089;5,8;180715;963090;4,3;0;0;5;397,8;55;20;114;9,6;1947;1,4;2290;3,2;11282;3;20754;7,7;33710;2,7;356950;2,2;BL 534 9058;; +20211021;VENDREDI;12/03/2021;;11/06/2021;11;4;6;44;9;10;11;-4-6-9-11-44-;-10-11-;0;0;0;1;7;123876,4;4;13;15589,5;7;35;1803,5;286;1098;105,9;354;1749;70,2;849;3167;27,2;4782;24117;17,9;10247;44346;10,8;33016;131304;6,8;22726;120258;9;126272;579055;5,9;392642;1684823;3,2;1;1468,7;4;293,7;117;12,5;144;10,1;1957;1,8;4092;2,4;9220;5;14217;15;50468;2,4;357030;2,9;PH 942 4010;; +20211020;MARDI ;09/03/2021;;08/06/2021;16;21;33;32;1;11;12;-1-16-21-32-33-;-11-12-;0;0;0;0;2;306703,5;0;5;28672,6;5;41;1089,1;85;477;172,4;192;1043;83,3;256;1386;44;2810;15161;20,1;4546;23387;14,5;13416;67182;9,4;15170;80486;9,5;69691;351440;6,8;218161;1035128;3,7;0;0;3;651,8;42;25,8;69;15,7;1208;2,2;1873;3,8;6369;5,3;10660;14,8;29082;3;263579;2,9;WG 461 7511;; +20211019;VENDREDI;05/03/2021;;04/06/2021;4;46;5;39;48;7;10;-4-5-39-46-48-;-7-10-;0;0;0;1;3;267994,9;0;5;37580,8;3;23;2544,6;101;638;168,9;319;1451;78,5;271;1338;59,8;5196;22791;17,5;6351;30928;14,4;13513;68082;12,2;31818;133487;7,5;107415;499764;6,3;235398;1129766;4,5;0;0;0;0;45;89,4;137;10,4;2096;1,7;2593;3,7;12598;3,5;22241;9,4;43070;2,7;448629;2,3;OM 097 5007;; +20211018;MARDI ;02/03/2021;;01/06/2021;24;49;19;26;2;6;7;-2-19-24-26-49-;-6-7-;0;0;0;0;2;296930,7;0;5;27759;6;30;1441;180;712;111,8;358;1537;54,7;374;1470;40,2;4829;21358;13,8;8142;33011;9,9;14486;64927;9,4;25683;114577;6,4;110452;471779;4,9;201547;928312;4;0;0;5;387,5;72;14,9;136;7,9;1852;1,4;3309;2,1;10456;3,2;16354;9,6;43957;2;356354;2,1;NS 960 1970;; +20211017;VENDREDI;26/02/2021;;28/05/2021;6;12;22;29;33;11;6;-6-12-22-29-33-;-6-11-;0;1;210000000;3;12;2494703,8;11;35;11695,7;33;108;1180,5;732;2406;97,6;1311;4760;52,1;1575;5419;32,1;17861;63073;13,8;29202;101722;9,5;64848;227281;7,9;84861;310363;7;370590;1334324;5,1;848606;3053393;3,6;1;3533,4;12;235,5;282;12,5;493;7,1;6377;1,3;10498;2,2;31325;3,5;47650;10,8;134916;2,1;960604;2,6;KF 243 5930;; +20211016;MARDI ;23/02/2021;;25/05/2021;30;9;39;18;45;3;1;-9-18-30-39-45-;-1-3-;0;0;0;0;6;3519853,7;0;9;37306,4;17;69;1515,6;434;1662;115,9;765;3152;64,6;939;3869;36,9;11195;46060;15,5;18214;72114;11;40465;161187;9,2;62448;252839;7,1;251409;1002759;5,6;554386;2239261;4;0;0;6;809,2;158;17;275;9,8;4062;1,6;6450;2,8;22320;3,8;37227;10,5;89621;2,4;801452;2,4;PI 915 7042;; +20211015;VENDREDI;19/02/2021;;21/05/2021;25;48;4;46;12;12;7;-4-12-25-46-48-;-7-12-;0;0;0;2;9;177571,4;3;15;24900,8;19;65;1789,8;473;1645;130,2;849;3260;69,4;940;3619;43,9;12709;46931;16,9;19859;73511;12;43863;165838;9,9;66686;249124;8;291007;1070213;5,8;643174;2450775;4,1;2;1559,3;9;277,2;175;17,8;332;9,3;4834;1,6;7111;2,9;25073;3,9;40248;11,3;105031;2,4;926213;2,4;PC 315 9670;; +20211014;MARDI ;16/02/2021;;18/05/2021;30;25;29;9;5;6;7;-5-9-25-29-30-;-6-7-;0;0;0;1;5;200974,1;3;17;13814,9;15;75;975,3;348;1515;88,9;592;2740;51,9;651;3204;31,2;8189;38901;12,8;13976;62736;8,8;26266;124483;8,3;39536;193196;6,5;186066;850401;4,6;342689;1678423;3,8;0;0;4;680,3;108;13,9;214;7;2902;1,3;4927;2;14140;3,3;22067;10;66011;1,8;476811;2,3;RC 048 0036;; +20211013;VENDREDI;12/02/2021;;14/05/2021;32;7;36;30;23;12;8;-7-23-30-32-36-;-8-12-;0;0;0;1;3;406714,9;6;13;21935,9;14;60;1480,3;249;1186;137,9;457;2328;74,3;597;3014;40,3;6854;33839;17,9;11433;56573;11,9;29957;142207;8,8;35447;177849;8,5;165355;810020;5,9;431681;2047679;3,7;1;1948,7;3;519,6;93;20,9;179;10,8;2524;1,9;4269;3;13331;4,6;22215;12,8;59730;2,6;528659;2,6;SY 373 9746;; +20211012;MARDI ;09/02/2021;;11/05/2021;24;5;31;10;37;9;10;-5-10-24-31-37-;-9-10-;0;0;0;0;1;934794,5;3;8;27309,6;13;56;1215,1;241;1078;116,2;321;1787;74,1;549;2494;37,3;5195;25963;17,9;10362;47850;10,8;23966;109736;8,8;26585;134742;8,6;143065;667850;5,5;336356;1553129;3,8;0;0;4;666,5;93;15,9;111;13,3;1983;1,8;3634;2,7;9970;4,6;15596;13,8;51792;2,3;414044;2,6;ME 065 0382;; +20211011;VENDREDI;05/02/2021;;07/05/2021;45;12;37;19;44;6;2;-12-19-37-44-45-;-2-6-;0;0;0;2;9;151022,6;1;12;26472,3;22;113;875,6;466;1610;113,2;950;3814;50,5;893;3449;39,2;14317;55212;12,2;18158;68836;10,9;36499;147030;9,5;74630;287111;5,9;253469;980351;5,4;520565;2108737;4;1;2517,5;12;167,8;172;14,6;322;7,8;4946;1,2;6383;2,6;25897;3;42520;8,6;90068;2,3;761007;2,4;EJ 459 7231;; +20211010;MARDI ;02/02/2021;;04/05/2021;38;18;48;20;35;9;12;-18-20-35-38-48-;-9-12-;0;0;0;0;1;576287;3;10;13468,7;4;28;1498,2;95;525;147,1;169;985;82,9;235;1251;45,8;2405;13819;20,7;4344;23549;13,5;11345;59074;10;14127;76406;9,4;64261;338644;6,7;164315;838385;4,3;0;0;2;828,6;35;26,3;68;13,5;967;2,3;1720;3,5;5703;5;9825;13,6;24907;3;258211;2,5;NU 587 9239;; +20211009;VENDREDI;29/01/2021;;30/04/2021;27;36;5;42;1;6;1;-1-5-27-36-42-;-1-6-;0;0;0;0;3;238052,2;2;4;41727,5;5;30;1732,9;133;667;143,5;347;1702;59,4;282;1549;45,9;5645;25376;14;7147;33713;11,7;14888;74940;9,8;29030;132397;6,7;111122;508307;5,5;233780;1128677;4;0;0;2;1024,8;40;28,4;116;9,8;1981;1,4;2580;2,9;10410;3,4;15895;10,4;40947;2,2;327773;2,5;FS 781 4350;; +20211008;MARDI ;26/01/2021;;27/04/2021;7;37;25;40;5;2;8;-5-7-25-37-40-;-2-8-;0;0;0;0;0;0;1;5;122065,6;5;25;1440,5;114;595;111,4;337;1670;41,9;264;1376;35,8;4975;23815;10,3;5096;26029;10,5;10990;58012;8,8;25877;124550;4,9;75464;373011;5,2;164296;824253;3,8;0;0;0;0;48;46,3;140;5,6;1824;1;1888;2,8;9564;2,6;15530;7,4;28431;2,3;234417;2,4;EF 410 9474;; +20211007;VENDREDI;22/01/2021;;23/04/2021;16;47;42;8;44;7;6;-8-16-42-44-47-;-6-7-;0;1;80566353;0;1;797450,9;0;2;93188,7;4;35;1658,6;143;722;148,1;354;1674;67,5;301;1599;49,6;5250;26200;15,1;7559;36769;12;13971;74683;11;29745;144978;6,8;118153;563565;5,5;219576;1133936;4,4;0;0;0;0;50;68,5;123;9,9;1857;1,6;2734;3;10471;3,6;18761;9,5;42195;2,3;420764;2,1;DR 848 4786;; +20211006;MARDI ;19/01/2021;;20/04/2021;25;35;31;27;43;5;6;-25-27-31-35-43-;-5-6-;0;0;0;1;6;99196,6;1;4;34775,8;5;23;1883,7;100;546;146,1;201;1282;65,8;223;1206;49,1;3209;19246;15,4;5212;27536;12;10352;55707;11;18240;103841;7,1;80696;425335;5,5;159249;843964;4,4;0;0;2;818,1;40;22,7;77;11,8;1190;1,9;1823;3,3;6693;4,2;11955;11,1;29708;2,5;297118;2,2;CD 285 4461;; +20211005;VENDREDI;15/01/2021;;16/04/2021;27;10;4;40;38;11;3;-4-10-27-38-40-;-3-11-;0;0;0;0;2;384315,1;1;3;59880,5;7;44;1271,6;161;827;124,6;342;1727;63;390;1972;38,8;4934;24461;15,6;7495;37538;11,3;18304;87508;9;25892;127458;7,5;110316;541887;5,5;265732;1251776;3,9;0;0;4;551,9;54;22,7;130;9,4;1834;1,6;2705;3;9684;3,9;15985;11,2;40408;2,5;350796;2,5;VK 667 2191;; +20211004;MARDI ;12/01/2021;;13/04/2021;10;34;2;35;18;6;9;-2-10-18-34-35-;-6-9-;0;0;0;1;1;562879,7;0;4;32888,5;11;25;1639;107;607;124,3;255;1384;57,6;293;1397;40,1;4248;20787;13,4;5226;25458;12,2;11672;56481;10,3;23600;117669;5,9;79536;385051;5,7;174019;844610;4,2;0;0;2;817,2;43;21,1;86;10,5;1534;1,4;1885;3,2;8606;3,3;15286;8,6;29790;2,5;275716;2,3;KP 202 6170;; +20211003;VENDREDI;08/01/2021;;09/04/2021;42;23;37;41;18;4;6;-18-23-37-41-42-;-4-6-;0;0;0;1;4;180403,7;1;6;28108,8;8;47;1117,6;160;723;133,8;383;1717;59,5;282;1473;48,8;6686;28209;12,7;6144;31800;12,6;13211;68850;10,8;37172;153197;5,9;100139;482668;5,9;215691;1057516;4,3;0;0;0;0;56;59,3;139;8,5;2373;1,2;2197;3,6;13218;2,8;22859;7,5;36396;2,6;361942;2,3;AZ 574 5485;; +20211002;MARDI ;05/01/2021;;06/04/2021;37;42;12;34;38;3;1;-12-34-37-38-42-;-1-3-;0;0;0;2;4;131718,1;0;0;0;2;16;10093,3;79;441;160,2;210;1054;70,8;170;971;54;2993;14869;17,6;4792;22652;12,9;10898;52480;10,3;18240;87049;7,5;69834;335506;6,1;155098;759051;4,4;1;876,9;1;701,5;27;32,4;82;10,6;1143;1,9;1725;3,4;6721;4,1;13072;9,7;26201;2,7;277668;2,2;DF 777 9665;; +20211001;VENDREDI;01/01/2021;;02/04/2021;32;16;48;44;28;1;9;-16-28-32-44-48-;-1-9-;0;1;44958140;1;8;109750,6;0;10;20520,4;9;47;1359,9;166;1008;116,8;315;1879;66,2;395;2435;35,9;5195;28333;15,4;6820;39885;12,2;16649;96183;9,4;29766;156929;7;98958;565121;6,1;235126;1323070;4,2;0;0;5;442,1;62;19,8;111;11;1868;1,6;2466;3,3;10317;3,7;19712;9;35761;2,8;370809;2,4;UA 000 7122;; +20200104;MARDI ;29/12/2020;;29/04/2021;7;40;39;45;32;8;3;-7-32-39-40-45-;-3-8-;0;0;0;0;1;581409,4;0;4;33971,2;9;28;1511,6;96;505;154,3;275;1331;61,9;185;1011;57,2;3970;21753;13,3;4705;24993;12,9;10143;53878;11,1;23845;125339;5,8;72556;384162;5,9;153496;814532;4,5;0;0;3;554,7;37;24,9;100;9,2;1432;1,6;1785;3,4;8664;3,3;16444;8,2;27244;2,7;301413;2,2;TB 433 2954;; +20200103;VENDREDI;25/12/2020;;25/04/2021;16;21;27;30;32;3;5;-16-21-27-30-32-;-3-5-;0;0;0;0;0;0;0;4;207160,1;5;55;888,9;124;812;110,9;460;2357;40,3;302;1613;41,4;6674;32983;10,1;7097;36790;10,1;13805;70599;9,8;35812;170909;4,9;107042;533541;4,9;206645;1026611;4,1;0;0;3;637,7;47;22,6;162;6,5;2260;1,1;2522;2,8;12261;2,7;19482;7,9;37624;2,3;332260;2,3;LH 798 2665;; +20200102;MARDI ;22/12/2020;;22/04/2021;42;10;50;2;16;11;4;-2-10-16-42-50-;-4-11-;0;1;43704037;1;4;156220,3;0;3;48681,6;3;15;3032,6;125;593;141,3;239;1273;69,5;303;1435;43,3;3347;18373;16,9;5183;26586;13;13685;66274;9,7;18570;99104;7,8;79770;403099;6,1;204328;995329;3,9;1;1006,2;2;402,5;36;27,9;108;9,3;1341;1,8;2032;3,3;7267;4,3;12393;11,8;30822;2,6;278323;2,6;MI 502 5000;; +20200101;VENDREDI;18/12/2020;;18/04/2021;2;21;17;3;13;7;8;-2-3-13-17-21-;-7-8-;0;0;0;2;4;192267,3;1;10;17974,4;22;80;699,8;435;1666;61,9;575;2495;43,6;667;2806;27,3;7011;30918;12,3;14939;62830;6,8;26297;111763;7,1;31474;142041;6,7;178955;757863;4;314233;1390130;3,5;1;1322,9;5;211,6;171;7,7;204;6,4;2510;1,3;5357;1,6;11380;3,6;17053;11,3;64482;1,6;402642;2,3;QJ 740 0867;; +20200100;MARDI ;15/12/2020;;15/04/2021;21;9;35;13;29;2;1;-9-13-21-29-35-;-1-2-;0;0;0;0;7;81129,1;1;11;12066,2;3;21;1968,6;196;783;97,2;286;1283;62,7;392;1907;29,6;3692;18232;15,5;7601;33176;9,5;16989;76794;7,6;18441;92101;7,7;98775;441028;5;219416;1008542;3,5;0;0;3;589,8;73;13,4;111;8,8;1381;1,7;2893;2,2;6896;4,4;11052;12,9;37738;2,1;285839;2,5;AA 954 3153;; +20200099;VENDREDI;11/12/2020;;11/04/2021;41;13;24;9;6;3;12;-6-9-13-24-41-;-3-12-;1;1;200000000;3;12;2364169,1;5;17;22819,6;20;73;1655,2;574;2271;98;957;3764;62,5;1373;5278;31,3;12455;50834;16,2;24341;91723;10;58141;219032;7,8;62053;258453;8;314250;1224812;5,3;753910;2892569;3,6;1;3052;8;305,2;230;13,2;349;8,7;4577;1,6;8495;2,4;22740;4,2;36831;12;109852;2,2;850017;2,6;OR 273 6030;; +20200098;MARDI ;08/12/2020;;08/04/2021;1;46;21;4;24;2;12;-1-4-21-24-46-;-2-12-;0;0;0;2;4;5684144,4;4;16;19431,3;18;65;1489,8;347;1319;135,2;703;2616;72;815;3106;42,6;9825;38177;17,3;14866;58844;12,5;36647;142857;9,6;52688;203482;8,1;222926;874394;6;549225;2132408;3,9;1;2464;7;281,6;127;19,4;236;10,4;3510;1,7;5197;3,1;19157;4;31567;11,3;78502;2,5;692981;2,5;UF 576 9619;; +20200097;VENDREDI;04/12/2020;;04/04/2021;14;38;20;27;34;1;11;-14-20-27-34-38-;-1-11-;0;0;0;4;6;1385679,2;8;26;15425,5;20;76;1643,7;499;1747;131,7;917;3422;71;1292;4703;36,3;13455;50925;16,7;21149;77496;12,3;56440;204601;8,6;71306;271544;7,9;300626;1107720;6,1;778710;2840869;3,8;3;1103,8;6;441,5;178;18,6;301;11;4608;1,7;7046;3,1;24459;4,2;41614;11,6;101950;2,6;895540;2,6;DN 528 1278;; +20200096;MARDI ;01/12/2020;;01/04/2021;29;49;20;14;47;12;4;-14-20-29-47-49-;-4-12-;0;0;0;0;3;328905,6;1;6;38435,3;10;53;1355,2;144;821;161,1;319;1848;75,6;378;1967;49,9;4698;26355;18,6;7382;39794;13,7;18544;97165;10,5;26020;143597;8,6;115063;606731;6,4;291381;1502470;4,1;0;0;3;833,1;46;30,1;122;11,3;1733;2;2620;3,5;9557;4,5;15848;12,7;40468;2,8;382566;2,6;FV 079 1121;; +20200095;VENDREDI;27/11/2020;;28/03/2021;8;14;16;5;2;9;8;-2-5-8-14-16-;-8-9-;0;0;0;1;3;391023,1;5;28;9791,6;24;90;948,8;583;2157;72,9;632;2979;55,8;1170;4312;27,1;8245;40603;14,3;18091;77208;8,4;35174;153979;7,8;37932;196484;7,4;208511;976295;4,7;426947;1999670;3,7;1;1673,4;8;167,3;189;8,8;213;7,8;2870;1,4;6080;1,8;13218;3,9;20020;12,2;70405;1,9;483025;2,5;VG 924 9237;; +20200094;MARDI ;24/11/2020;;25/03/2021;50;33;38;42;25;12;8;-25-33-38-42-50-;-8-12-;0;0;0;1;5;176840,4;4;12;17221;7;40;1609,1;151;830;142,8;319;1615;77,6;416;2012;43,7;4485;23825;18,4;7044;37825;12,9;17269;90012;10,1;23507;125229;8,8;103129;552652;6,3;252717;1321424;4,2;0;0;2;1151;62;20,6;92;13,9;1574;2;2406;3,5;8363;4,8;14727;12,6;36176;2,9;361397;2,5;SP 913 9425;; +20200093;VENDREDI;20/11/2020;;20/01/2021;28;50;48;39;29;5;7;-28-29-39-48-50-;-5-7-;0;0;0;2;6;209127,7;2;7;41894,2;6;61;1497,4;225;1144;147;640;3110;57,1;553;2605;47,9;10992;49635;12,5;11040;53181;13,1;23420;113770;11,4;70409;298808;5,2;181677;842595;5,8;369721;1730636;4,6;0;0;0;0;86;66,2;228;8,9;3678;1,3;3777;3,6;23189;2,7;44279;6,7;62049;2,7;682220;2,1;FT 835 8023;; +20200092;MARDI ;17/11/2020;;17/01/2021;30;25;44;19;16;6;2;-16-19-25-30-44-;-2-6-;0;0;0;1;1;609799,2;0;4;35630;16;46;965;147;750;109;362;1896;45,5;306;1576;38,5;5462;26592;11,4;6556;32885;10,3;13607;70609;8,9;27954;135999;5,6;93962;464581;5,1;192599;982134;3,9;0;0;8;202,1;48;18,7;113;7,9;1927;1,1;2285;2,6;9695;2,9;15529;8,4;33277;2,2;271015;2,4;DQ 513 0923;; +20200091;VENDREDI;13/11/2020;;13/01/2021;31;17;1;28;5;1;10;-1-5-17-28-31-;-1-10-;0;0;0;1;4;222454,1;8;26;7998,6;16;56;1156,7;395;1328;89,8;589;2073;60,8;925;3283;26,9;7409;27330;16,2;14440;50710;9,7;34975;125599;7,3;33783;133357;8,3;172944;644710;5,4;432727;1633896;3,4;0;0;5;565,6;134;11,7;183;8,5;2543;1,5;4873;2,1;11664;4,2;17174;13,3;58235;2,2;395681;2,8;AX 115 7025;; +20200090;MARDI ;10/11/2020;;10/01/2021;19;3;38;32;29;12;5;-3-19-29-32-38-;-5-12-;0;0;0;0;2;279894,1;0;3;43610,5;3;21;1940,5;87;529;141,9;188;1084;73,2;272;1360;41;2534;15129;18,4;4578;26244;11,8;11795;62688;9,2;13810;79417;8,8;68965;381713;5,7;180414;927880;3,8;0;0;0;0;24;95,7;77;10,6;985;2;1609;3,4;5343;4,8;8972;13,3;25271;2,6;220804;2,7;IB 051 3569;; +20200089;VENDREDI;06/11/2020;;06/01/2021;12;37;7;50;40;1;2;-7-12-37-40-50-;-1-2-;0;0;0;0;2;347604,2;0;6;27080,2;8;41;1234,3;140;725;128,5;244;1336;73,7;246;1488;46,5;3830;21182;16,3;6030;32150;12;13033;72026;9,9;20868;112945;7,7;94502;484902;5,6;205321;1091274;4;0;0;2;899,4;54;18,5;93;10,7;1305;1,9;2149;3,1;7245;4,3;11418;12,7;33638;2,4;299201;2,4;LB 981 4400;; +20200088;MARDI ;03/11/2020;;03/01/2021;5;7;18;20;30;7;6;-5-7-18-20-30-;-6-7-;0;0;0;2;7;70929,1;4;14;8288,6;7;43;840,5;273;1128;59;387;1664;42,2;450;1939;25,5;4548;21016;11,7;9256;39142;7;14873;69786;7,3;19898;99060;6,2;102303;470113;4,1;172514;869851;3,6;0;0;4;325,4;93;7,7;144;5;1631;1,1;3344;1,4;7195;3,1;9943;10,6;37039;1,6;225500;2,3;SB 380 8153;; +20200087;VENDREDI;30/10/2020;;30/12/2020;21;16;20;12;28;9;3;-12-16-20-21-28-;-3-9-;0;0;0;0;4;163187;2;8;19069,7;10;54;879,9;255;1047;83,6;635;2536;36,4;494;2083;31,2;7688;33733;9,6;9213;41198;8,8;18627;83988;8;34772;165331;4,9;114267;541770;4,7;230929;1105289;3,7;0;0;3;568;77;12,2;216;4,3;2727;0,8;3214;1,9;12193;2,4;17994;7,6;39495;1,9;274932;2,5;DK 004 2337;; +20200086;MARDI ;27/10/2020;;27/12/2020;13;15;28;44;32;12;3;-13-15-28-32-44-;-3-12-;0;1;87757466;1;4;152128,9;1;9;15802,2;2;24;1845,7;149;735;111;225;1152;74,8;325;1869;32,4;3090;17011;17,8;5677;30567;11;14613;76077;8,2;15228;87237;8,7;79290;421362;5,6;202029;1050009;3,6;1;860;0;0;48;32,2;76;11,3;1197;1,7;2007;2,8;5811;4,6;9108;13,7;28488;2,4;231716;2,6;QA 581 7004;; +20200085;VENDREDI;23/10/2020;;23/12/2020;19;23;10;21;15;3;12;-10-15-19-21-23-;-3-12-;0;0;0;1;3;261553,3;6;26;7053,3;4;31;1842,6;304;1410;74,6;292;1612;69;855;3375;23,1;4271;22625;17,2;10719;49699;8,7;27543;124813;6,5;19732;110757;8,8;126094;622490;4,9;324275;1551438;3,2;0;0;3;664,5;109;10,1;117;9,4;1533;1,8;3577;2;7301;4,7;10991;14,7;43327;2,1;284222;2,8;KC 160 2843;; +20200084;MARDI ;20/10/2020;;20/12/2020;15;5;42;37;6;4;3;-5-6-15-37-42-;-3-4-;0;0;0;1;3;189400,6;3;7;18971,1;3;23;1798,4;110;586;130;225;1151;69,9;230;1286;44;3375;18004;15,7;5359;28060;11,2;10998;58880;9,9;18169;97757;7,2;82769;426910;5,2;169420;878501;4,1;0;0;2;735,3;36;22,6;65;12,5;1185;1,7;1843;2,9;6489;3,9;10325;11,5;28898;2,3;254502;2,3;MW 147 9033;; +20200083;VENDREDI;16/10/2020;;16/12/2020;15;40;38;33;50;6;3;-15-33-38-40-50-;-3-6-;0;0;0;1;1;737995,3;0;5;34496,3;5;50;1074,4;122;691;143,2;316;1693;61,7;257;1440;51;5351;26653;13,7;5536;31378;13;12004;67950;11,2;34606;157351;5,8;90154;486562;5,9;187505;1029066;4,5;1;1089,1;1;871,3;38;28,6;106;10,2;1824;1,4;1922;3,7;11627;2,9;22917;6,9;31330;2,8;360338;2,1;RV 734 1637;; +20200082;MARDI ;13/10/2020;;13/12/2020;5;41;46;38;14;10;1;-5-14-38-41-46-;-1-10-;0;0;0;0;1;522745,9;0;1;122174,3;1;19;2002,8;90;410;170,9;193;949;78;205;1067;48,8;2594;12725;20,4;3973;19911;14,5;9928;51243;10,5;14613;72442;9;60077;296170;6,9;155654;772207;4,3;0;0;0;0;30;77;68;12,1;977;2,1;1481;3,7;5345;4,8;9607;12,5;22030;3;226075;2,6;CV 058 7277;; +20200081;VENDREDI;09/10/2020;;09/12/2020;15;11;35;50;41;8;5;-11-15-35-41-50-;-5-8-;0;0;0;0;2;347164,3;0;3;54092;2;44;1148,7;97;677;137,5;370;1993;49,3;260;1365;50,6;6064;31728;10,8;5685;31487;12,2;12002;62584;11,4;36411;180330;4,8;91006;477696;5,7;192059;969596;4,5;0;0;1;1936,9;43;25;132;8,1;2001;1,3;1992;3,6;12200;2,7;21643;7,2;31557;2,8;333609;2,3;KQ 131 6639;; +20200080;MARDI ;06/10/2020;;06/12/2020;41;36;4;21;47;11;9;-4-21-36-41-47-;-9-11-;0;0;0;0;3;165112,6;1;5;23153,7;2;16;2253,6;61;395;168,1;159;887;79,1;171;943;52,3;2347;13056;18,8;3349;19244;14,2;7959;43992;11,6;13176;73158;8,4;54207;295086;6,6;132587;686274;4,5;0;0;1;1402,8;22;35,4;60;12,9;879;2,2;1219;4,2;5098;4,8;9236;12,3;20083;3,2;226457;2,5;AK 982 0660;; +20200079;VENDREDI;02/10/2020;;02/12/2020;15;45;12;6;40;9;3;-6-12-15-40-45-;-3-9-;1;1;30627859;7;8;88921,7;3;8;20782,4;6;48;1078,8;171;762;125,1;489;2195;45,9;298;1502;47,1;7218;32321;10,9;7362;34706;11,3;15359;73110;10;36650;169534;5,2;111724;524166;5,3;231569;1086594;4,1;7;155,9;3;291;62;17,6;151;7,2;2559;1;2509;2,9;12405;2,7;21149;7,5;38844;2,3;329183;2,4;DO 142 6191;; +20200078;MARDI ;29/09/2020;;29/11/2020;16;14;4;44;41;11;12;-4-14-16-41-44-;-11-12-;0;0;0;0;1;511878,3;0;6;19939;5;16;2328,9;81;450;152,5;133;857;84,6;253;1430;35,6;2019;11989;21,2;3468;19701;14,4;11716;59701;8,8;11140;64119;10;52294;278746;7,2;174069;862108;3,7;0;0;1;1420,1;27;29,2;51;15,4;783;2,5;1330;3,9;4295;5,7;7371;15,6;19893;3,2;184849;3,1;LH 000 7642;; +20200077;VENDREDI;25/09/2020;;25/11/2020;34;45;19;23;37;1;7;-19-23-34-37-45-;-1-7-;0;1;130000000;0;5;247047,8;5;25;11547,8;16;78;1152,8;374;1590;104,1;812;3162;55,3;801;3694;33,3;10810;44420;13,8;14416;63635;10,7;30469;142425;8,9;59230;237240;6,5;195280;867562;5,6;412900;1921549;4;0;0;5;689,5;125;15,3;222;8,6;3482;1,3;4561;2,8;19268;3,1;34308;8,1;63749;2,4;601772;2,3;GB 600 8763;; +20200076;MARDI ;22/09/2020;;22/11/2020;28;39;25;40;16;1;6;-16-25-28-39-40-;-1-6-;0;0;0;1;5;123979;0;3;48293,2;7;21;2148,8;110;600;138,5;217;1285;68,3;234;1443;42,7;3967;20023;15,4;5224;27225;12,6;12008;64322;9,9;21562;107403;7,2;78996;404454;6;179553;944456;4,1;0;0;3;538,9;41;21,9;81;11;1331;1,6;1808;3,3;7167;3,9;12753;10,2;27602;2,6;269829;2,4;AM 862 6424;; +20200075;VENDREDI;18/09/2020;;18/11/2020;25;45;19;2;10;1;7;-2-10-19-25-45-;-1-7-;0;0;0;0;5;166116;2;6;32353,3;11;61;991,2;266;1158;96,1;581;2426;48,5;482;2436;33,9;7268;33330;12,4;9713;46944;9,8;19803;101487;8,4;35586;166868;6,2;128995;646705;5;254921;1368514;3,8;0;0;3;652,4;96;11,3;179;6;2424;1,1;3155;2,3;11760;2,9;18338;8,6;43220;2;329124;2,4;NL 914 1916;; +20200074;MARDI ;15/09/2020;;15/11/2020;38;25;30;16;20;8;6;-16-20-25-30-38-;-6-8-;0;0;0;0;3;188472;1;7;18878,1;6;36;1143,3;96;655;115,7;302;1541;52;254;1344;41,9;4582;22037;12,7;5817;29331;10,7;11904;60360;9,6;23906;113496;6,2;83183;414876;5,3;172473;873152;4,1;0;0;0;0;41;57,2;96;8,7;1601;1,3;2052;2,7;8397;3,1;14151;8,6;28917;2,3;256988;2,3;OP 735 6223;; +20200073;VENDREDI;11/09/2020;;11/11/2020;34;38;45;43;42;12;9;-34-38-42-43-45-;-9-12-;0;0;0;1;2;352375,3;0;8;20588,9;7;25;2052,1;118;580;162,9;207;1055;94,6;300;1426;49,2;2921;16223;21,6;4868;25778;15,1;13238;62894;11,5;15678;88055;10;72176;389847;7,1;191835;969300;4,6;1;1049,2;4;209,8;42;24,9;80;13,1;1127;2,3;1748;4;5938;5,5;11161;13,7;25958;3,3;303075;2,5;ES 198 7144;; +20200072;MARDI ;08/09/2020;;08/11/2020;9;35;2;41;31;12;9;-2-9-31-35-41-;-9-12-;0;0;0;0;2;261831;1;4;30597,1;3;23;1657,4;82;428;164;131;821;90,4;163;987;52,8;2218;12450;20,9;3896;20444;14,2;9250;48658;11,1;12552;68440;9,5;61601;320138;6,4;154660;783822;4,2;0;0;1;1436,8;33;24,1;43;18,5;862;2,3;1417;3,7;4940;5;7899;14,7;22948;2,8;210923;2,7;UZ 647 1420;; +20200071;VENDREDI;04/09/2020;;04/11/2020;6;29;43;40;22;7;4;-6-22-29-40-43-;-4-7-;0;0;0;1;4;176702,3;1;8;20649,1;11;38;1354;148;674;140,6;386;1866;53,6;295;1500;46,9;6113;28936;12,1;6601;32338;12,1;12870;68089;10,7;35686;163884;5,4;99342;491615;5,6;192911;1013742;4,4;0;0;2;917,1;46;22,1;125;8,1;2078;1,2;2355;2,8;12144;2,6;21353;6,9;34723;2,4;337427;2,1;BC 472 0015;; +20200070;MARDI ;01/09/2020;;01/11/2020;46;8;4;33;10;8;11;-4-8-10-33-46-;-8-11-;1;1;157170843;1;6;153948,4;2;6;35980,2;4;44;1528,2;170;1006;123,1;337;1973;66,3;358;2050;44,8;5073;28911;15,9;8898;45746;11,2;19409;100330;9,5;26897;152250;7,6;124437;654019;5,5;292633;1486934;3,9;1;1240,3;2;496,1;58;21,3;117;10,6;1721;1,8;3018;2,7;9163;4,2;14511;12,4;42134;2,4;337858;2,6;IR 486 1016;; +20200069;VENDREDI;28/08/2020;;27/11/2020;31;16;17;7;12;9;7;-7-12-16-17-31-;-7-9-;0;0;0;2;9;119361,1;4;18;13948,3;28;104;751,9;399;1731;83,2;957;4142;36,7;689;3228;33,1;12826;56369;9,4;15772;73247;8,1;26644;132973;8,3;56107;261389;5,1;198151;974840;4,3;354133;1815616;3,7;1;1400,7;9;124,5;121;11,5;300;4,6;4063;0,8;5160;1,8;18268;2,4;23713;8,6;64766;1,7;406770;2,5;WF 229 9684;; +20200068;MARDI ;25/08/2020;;24/11/2020;18;37;39;30;7;10;8;-7-18-30-37-39-;-8-10-;0;0;0;1;5;163573,8;1;11;17377,2;9;47;1266,7;203;944;116,1;336;1746;66,4;405;2037;39,9;5019;25376;16;7748;40006;11,3;17673;92808;9,1;25923;132800;7,7;107822;564682;5,7;258534;1345481;3,8;0;0;4;503;68;16,4;115;9,7;1704;1,6;2592;2,8;9190;3,8;15399;10,5;36199;2,5;313766;2,5;QF 215 6243;; +20200067;VENDREDI;21/08/2020;;20/11/2020;10;35;50;15;44;5;3;-10-15-35-44-50-;-3-5-;0;0;0;1;6;156509,1;1;9;24385,8;12;47;1454,4;177;993;126,8;484;2610;51;336;1978;47,2;7466;41568;11,2;7769;44889;11,6;15925;89552;10,8;41785;219696;5,3;125690;679218;5,4;259095;1364172;4,3;0;0;5;462,2;41;31,3;169;7,5;2374;1,3;2563;3,3;13383;3;23563;7,9;41583;2,5;408700;2,2;MH 105 6486;; +20200066;MARDI ;18/08/2020;;17/11/2020;12;34;42;17;6;12;5;-6-12-17-34-42-;-5-12-;0;0;0;1;5;131558,4;0;5;30747,3;4;30;1596,1;157;746;118,2;260;1354;68,8;427;1907;34,3;3313;18506;17,7;6678;32782;11,1;17248;80439;8,4;17511;96316;8,5;89387;457082;5,6;230608;1116083;3,7;0;0;1;1658,1;52;17,7;92;10;1170;1,9;2256;2,7;6250;4,6;9700;13,8;30562;2,4;241968;2,7;CC 440 6829;; +20200065;VENDREDI;14/08/2020;;13/11/2020;49;10;42;9;19;4;12;-9-10-19-42-49-;-4-12-;0;0;0;3;5;157446,8;1;5;36797,9;5;24;2387,8;127;670;157,5;224;1449;77;382;1810;43,3;3681;21133;18,5;6560;34068;12,8;18764;91743;8,8;18838;111645;8,8;98309;510793;6;271614;1349713;3,7;1;1050,7;2;420,3;37;28,3;90;11,6;1361;1,9;2303;3;6808;4,8;10682;14,3;33982;2,5;268117;2,8;EM 957 7653;; +20200064;MARDI ;11/08/2020;;10/11/2020;14;13;50;8;42;3;9;-8-13-14-42-50-;-3-9-;0;0;0;0;2;293190,7;0;2;68523,5;2;31;1376,9;88;530;148,3;284;1726;48,1;211;1237;47,2;5030;26334;11;4797;27592;11,8;10771;59707;10,1;27703;144296;5;80775;435472;5,3;173340;916256;4;0;0;0;0;28;79,8;88;9;1644;1,2;1670;3,2;9458;2,6;14940;7,8;27688;2,3;236254;2,4;RO 370 1123;; +20200063;VENDREDI;07/08/2020;;06/11/2020;36;20;5;41;21;11;6;-5-20-21-36-41-;-6-11-;0;0;0;1;4;195041,6;1;7;26048,2;4;36;1577,6;92;614;170,3;222;1516;72,9;243;1533;50,6;3876;23353;16,6;5099;32163;13,4;12635;76263;10,5;22376;129363;7,5;83306;497396;6,1;210211;1199060;4,1;1;992,4;1;793,9;38;26,1;72;13,7;1388;1,7;1751;3,7;8081;3,8;13855;10,4;28487;2,8;281356;2,5;HK 751 8334;; +20200062;MARDI ;04/08/2020;;03/11/2020;42;2;5;1;15;8;5;-1-2-5-15-42-;-5-8-;0;0;0;0;2;265150;0;1;123939,8;10;41;941,5;142;674;105,5;417;1933;38,8;253;1192;44,3;5651;28050;9,4;6297;31560;9,3;12579;62253;8,8;27523;141282;4,7;85100;427402;4,8;171178;843157;3,9;0;0;5;272,1;54;14;149;5;1970;0,9;2250;2,2;9200;2,5;13831;7,9;29735;2;217920;2,5;OK 001 5607;; +20200061;VENDREDI;31/07/2020;;30/10/2020;30;46;24;21;49;2;5;-21-24-30-46-49-;-2-5-;0;0;0;0;0;0;2;6;136226,5;4;27;1786,2;106;529;167,9;268;1506;62,3;219;1226;53,8;4661;24333;13,5;5122;27797;13,2;10658;58195;11,7;28448;145640;5,6;86106;450152;5,8;175195;919223;4,5;0;0;1;1680,4;29;32,1;93;10;1513;1,5;1819;3,4;9350;3,1;17056;7,9;29175;2,6;305557;2,2;DW 519 7337;; +20200060;MARDI ;28/07/2020;;27/10/2020;34;17;13;9;15;7;2;-9-13-15-17-34-;-2-7-;0;0;0;2;4;120865,8;1;7;16141,9;15;44;799,8;161;741;87,4;533;2093;32,7;298;1394;34,5;6614;28516;8,4;6721;32694;8,2;11856;60275;8,2;30848;135763;4,4;90012;434961;4,3;161071;826953;3,7;0;0;5;249,3;48;14,4;173;4;2190;0,7;2240;2;10671;2;14738;6,8;31149;1,8;212331;2,3;KI 833 7510;; +20200059;VENDREDI;24/07/2020;;23/10/2020;8;28;50;27;9;12;4;-8-9-27-28-50-;-4-12-;0;0;0;0;3;205961,3;0;7;20629,9;4;23;1955,6;94;555;149,2;196;1058;82,7;295;1595;38,5;2961;16672;18,4;5439;27279;12,5;14841;72778;8,7;15640;84996;9,1;85593;416403;5,8;238706;1125472;3,4;0;0;1;1605,1;35;25,4;72;12,3;1060;2,1;1914;3,1;5615;5;8487;15,3;29510;2,4;220156;2,9;JH 979 5073;; +20200058;MARDI ;21/07/2020;;20/10/2020;29;15;42;14;24;2;4;-14-15-24-29-42-;-2-4-;0;1;49564587;0;3;177754,5;2;3;41544,1;8;37;1049,1;89;555;128,8;304;1427;52,9;223;1204;44,1;4364;21507;12,3;5202;26912;11;10904;56710;9,7;22022;107554;6,2;80072;399902;5,2;163066;832577;4;0;0;3;457,1;39;19,5;95;8;1468;1,2;1831;2,7;7563;3,1;11689;9,5;27615;2,2;234707;2,3;RV 049 2923;; +20200057;VENDREDI;17/07/2020;;16/10/2020;7;27;41;15;43;7;9;-7-15-27-41-43-;-7-9-;0;0;0;3;7;96257,2;1;5;31495,6;13;53;925,4;196;957;94,4;550;2455;38,9;360;1739;38,5;7644;33820;9,9;8380;40886;9,1;14175;72686;9,5;37620;160145;5,2;118876;568827;4,6;208844;1030354;4,1;2;498,4;4;199,3;62;16;184;5,4;2625;0,9;2791;2,3;12504;2,5;18660;7,7;40266;2;311619;2,3;OU 340 6452;; +20200056;MARDI ;14/07/2020;;13/10/2020;30;22;37;11;47;9;10;-11-22-30-37-47-;-9-10-;0;0;0;0;0;0;2;6;101330,7;4;19;1888,1;81;487;135,6;137;818;85,4;215;1138;43,1;2027;12292;19,9;3956;21782;12,5;9547;51291;9,9;11098;66459;9,2;59649;325980;5,9;145134;778495;4;0;0;1;1227,5;26;26,2;46;14,8;762;2,2;1413;3,2;4077;5,2;6684;14,8;21512;2,6;195263;2,5;NY 772 1633;; +20200055;VENDREDI;10/07/2020;;09/10/2020;30;38;15;17;23;2;7;-15-17-23-30-38-;-2-7-;0;0;0;6;9;74781,9;4;14;11235,7;22;66;742,3;225;964;93,6;680;2649;36;413;1909;35,1;8672;36019;9,3;9628;42487;8,8;17021;80362;8,6;43749;177965;4,7;124906;573219;4,6;227549;1095560;3,9;0;0;7;266,9;66;15,7;221;4,6;2884;0,9;3243;2,1;14433;2,2;23282;6,5;41744;2;333163;2,2;UL 180 7837;; +20200054;MARDI ;07/07/2020;;06/10/2020;41;33;23;12;16;8;10;-12-16-23-33-41-;-8-10-;0;1;144542315;2;8;112962,6;2;16;13200,6;7;54;1218,2;214;1068;113,4;379;1861;68,8;499;2492;36,1;5595;27209;16,5;9380;45013;11,1;21980;108567;8,6;30484;143534;7,8;130601;631726;5,6;316109;1527227;3,7;2;654,4;3;349;75;17,4;121;10,8;1898;1,7;3083;2,8;10504;3,9;17011;11,2;42948;2,5;364199;2,6;JF 734 1511;; +20200053;VENDREDI;03/07/2020;;02/10/2020;27;4;16;39;37;3;6;-4-16-27-37-39-;-3-6-;0;0;0;1;10;126332,6;7;33;8947,2;26;98;938,4;336;1552;109,1;955;3713;48,2;671;3327;37,8;14036;52823;11,9;15598;66033;10,6;31922;138411;9,4;73256;275443;5,7;226377;940515;5,3;463034;1961364;4;0;0;6;620,8;102;20,2;295;7;4405;1,1;4919;2,8;22824;2,8;39083;7,7;72260;2,3;641409;2,3;SE 756 4471;; +20200052;MARDI ;30/06/2020;;29/09/2020;11;42;1;28;6;2;10;-1-6-11-28-42-;-2-10-;0;0;0;0;1;565114,1;5;16;8254,7;8;23;1788,6;168;654;115,8;328;1299;61,6;390;1688;33,3;4041;18420;15,2;6339;28487;11;16054;71847;8,1;20120;95075;7,4;85696;400514;5,5;209798;980320;3,6;0;0;1;1435,9;43;18,5;114;6,9;1483;1,3;2118;2,5;7179;3,5;11266;10,3;29410;2,2;216153;2,6;GL 973 8980;; +20200051;VENDREDI;26/06/2020;;25/09/2020;12;26;21;1;10;5;11;-1-10-12-21-26-;-5-11-;0;0;0;2;9;75644,7;9;18;8839,7;7;38;1304,2;248;1049;87;295;1633;59,1;719;2726;24,8;4116;22953;14,7;9253;41590;9;24020;98412;7,1;20362;117337;7,2;108449;530351;5;272674;1232996;3,5;1;914,7;1;731,8;75;12,1;104;8,7;1372;1,6;2992;2;6950;4,1;10699;12,4;35363;2,1;243546;2,7;OZ 006 2321;; +20200050;MARDI ;23/06/2020;;22/09/2020;19;46;49;12;2;8;1;-2-12-19-46-49-;-1-8-;0;0;0;0;0;0;1;3;201011,8;2;21;1694,4;90;487;134,5;252;1142;60,6;231;1120;43,4;3302;15908;15,3;4649;22351;12,1;10940;51961;9,7;17211;85896;7,1;68034;337786;5,7;156054;773857;4;0;0;1;1216,6;20;33,7;86;7,8;1158;1,4;1601;2,8;5846;3,6;9383;10,5;23069;2,4;191079;2,5;BD 519 4518;; +20200049;VENDREDI;19/06/2020;;18/09/2020;14;5;24;43;19;3;11;-5-14-19-24-43-;-3-11-;0;0;0;0;4;157456,5;2;8;18400;7;35;1309,9;147;744;113,5;311;1537;58;377;1722;36,4;3961;20217;15,5;6692;33500;10,4;16332;76928;8,4;19943;103921;7,5;93132;466080;5,3;228226;1083585;3,6;0;0;2;782,1;46;18,8;109;7,9;1341;1,6;2241;2,5;6845;3,9;10531;12;31138;2,3;233723;2,7;SD 890 4120;; +20200048;MARDI ;16/06/2020;;15/09/2020;39;9;11;4;17;2;10;-4-9-11-17-39-;-2-10-;0;0;0;0;2;228917,4;1;4;26750,8;4;16;2083;128;636;96,5;230;1128;57,5;378;1656;27,5;3256;15600;14,6;5517;26331;9,6;14398;68380;6,9;16006;76193;7,5;71327;353669;5,1;178563;879179;3,3;0;0;1;1176;39;16,7;92;7,1;1192;1,3;1942;2,2;5964;3,4;8813;10,8;24775;2,1;176330;2,6;GJ 375 0766;; +20200047;VENDREDI;12/06/2020;;11/09/2020;24;39;46;2;45;4;8;-2-24-39-45-46-;-4-8-;0;1;51583717;0;2;337141,5;1;3;52530,3;8;38;1291,7;83;565;160;310;1564;61,1;199;1158;58;4712;23702;14,1;5132;26919;13,9;10696;58277;11,9;28909;142367;5,9;81550;419915;6,3;175492;911820;4,7;0;0;4;423,1;32;29,3;88;10,6;1517;1,5;1706;3,6;9261;3,1;17661;7,7;27308;2,8;294256;2,3;TI 890 2631;; +20200046;MARDI ;09/06/2020;;08/09/2020;46;36;27;5;15;8;2;-5-15-27-36-46-;-2-8-;0;0;0;1;3;165913,8;0;2;58165,1;10;33;1098;83;550;121,3;262;1427;49,4;164;1095;45,2;4274;22240;11,1;4259;23196;11,9;9336;51249;10;23954;120489;5,1;67457;352621;5,5;144730;765947;4,1;1;692,9;5;110,8;26;26,6;94;7,3;1492;1,1;1471;3,1;8264;2,6;14182;7,1;22899;2,4;209867;2,4;BV 585 0710;; +20200045;VENDREDI;05/06/2020;;04/09/2020;24;11;37;17;5;6;3;-5-11-17-24-37-;-3-6-;0;0;0;0;3;211081,7;0;11;13454,5;13;67;688;254;1100;77,1;599;2434;36,8;448;2165;29,1;7135;31131;10,1;8812;42614;8,2;17509;84690;7,7;33144;149368;5,3;112688;545858;4,5;224871;1102940;3,6;0;0;3;521,5;77;11,2;164;5,3;2361;0,9;2768;2,1;10863;2,5;16171;7,8;36912;1,9;256110;2,4;OK 201 3475;; +20200044;MARDI ;02/06/2020;;01/09/2020;49;22;12;26;10;11;7;-10-12-22-26-49-;-7-11-;0;0;0;1;5;87507,8;2;9;11362,2;3;17;1873,6;132;547;107,2;209;1020;60,8;313;1288;33,8;2882;14935;14,5;5069;23592;10,3;11295;53698;8,4;14230;77349;7;67595;328824;5,2;150717;745473;3,7;0;0;0;0;39;43,4;73;8,2;1042;1,4;1771;2,2;5075;3,7;8157;10,8;23832;2;174043;2,5;FR 608 3244;; +20200043;VENDREDI;29/05/2020;;28/08/2020;4;11;8;46;19;4;8;-4-8-11-19-46-;-4-8-;0;1;17000000;2;5;116611,2;5;13;10482,2;47;137;309,8;203;897;87,1;909;3574;23,1;421;1730;33,5;7580;33182;8,7;8918;37668;8,5;16672;74615;8;27287;134296;5,4;104859;492834;4,6;205739;984731;3,7;0;0;20;72,7;72;11,2;303;2,6;2467;0,8;2981;1,8;8878;2,8;12465;9,4;35096;1,9;222958;2,6;MO 301 6631;; +20200042;MARDI ;26/05/2020;;25/08/2020;9;4;27;14;21;4;6;-4-9-14-21-27-;-4-6-;1;2;18571740;0;5;92895,4;2;6;18092,6;18;63;536,7;226;999;62,3;480;1993;33;412;1869;24,7;5468;24082;9,6;7345;33423;7,7;14138;67756;7;23358;106041;5,4;81565;400538;4,5;166638;842619;3,5;0;0;4;286,8;83;7,6;158;4;1901;0,8;2529;1,6;7990;2,5;11261;8,2;28189;1,8;178616;2,5;FV 414 5061;; +20200041;VENDREDI;22/05/2020;;21/08/2020;18;22;32;3;27;2;7;-3-18-22-27-32-;-2-7-;0;0;0;0;0;0;0;13;55708,2;9;53;806,2;150;842;93,4;557;2454;33,9;240;1496;39;7445;33462;8,7;7017;36228;9;12765;69537;8,7;35907;158062;4,6;99263;501467;4,6;180411;970867;3,8;0;0;3;469,1;49;15,9;171;4,5;2443;0,8;2285;2,2;11694;2,1;16640;6,8;32521;1,9;244998;2,3;ND 876 1904;; +20200040;MARDI ;19/05/2020;;18/08/2020;43;32;37;25;5;6;7;-5-25-32-37-43-;-6-7-;0;0;0;0;1;433824,4;1;6;16898,6;4;21;1503,8;76;467;124,5;159;981;62,6;162;934;46,2;2743;14287;15,1;3890;20912;11,5;7027;40300;11,1;16270;81398;6,6;60851;312561;5,4;110581;614114;4,4;0;0;2;561;22;28,3;56;11,1;928;1,6;1328;3,1;5603;3,5;9724;9,3;20928;2,4;218843;2;CU 770 5969;; +20200039;VENDREDI;15/05/2020;;14/08/2020;34;23;32;39;11;1;6;-11-23-32-34-39-;-1-6-;1;1;72969058;0;1;565971,2;2;6;22046,1;6;31;1329;161;740;102,5;318;1409;56,9;336;1844;30,5;4412;19013;14,8;6611;29810;10,5;14242;70025;8,3;24326;101435;6,9;85677;386395;5,7;193742;915870;3,9;0;0;3;544,8;47;19,3;107;8,4;1404;1,6;2290;2,6;7729;3,7;13147;10;28566;2,6;273571;2,4;TK 926 0069;; +20200038;MARDI ;12/05/2020;;10/09/2020;16;9;29;39;37;4;11;-9-16-29-37-39-;-4-11-;0;0;0;0;1;425436,6;1;11;9039,2;4;27;1147;114;482;118,3;174;1017;59,3;284;1228;34,5;2800;13634;15,5;4550;21054;11,2;11122;50289;8,7;14566;70785;7,5;62387;287434;5,8;156046;683566;3,9;0;0;1;1277,6;40;17,7;53;13,3;1024;1,7;1570;3;5212;4,2;8598;12;21318;2,7;196203;2,6;CN 495 0418;; +20200037;VENDREDI;08/05/2020;;06/09/2020;33;38;8;25;32;9;7;-8-25-32-33-38-;-7-9-;0;0;0;1;4;127034,9;0;2;59380,3;5;23;1608,3;103;481;141,6;280;1213;59,3;198;1018;49,7;4550;19060;13,2;4802;23726;11,8;9857;49676;10,5;27863;109056;5,8;79758;370064;5,4;155317;753374;4,2;0;0;2;718,6;32;24,9;86;9,2;1495;1,3;1597;3,3;9118;2,7;16620;7;26413;2,4;269695;2,1;WE 811 6067;; +20200036;MARDI ;05/05/2020;;03/09/2020;2;27;22;10;41;3;2;-2-10-22-27-41-;-2-3-;0;0;0;1;1;398633,9;1;2;46583,6;3;12;2418,2;146;611;87,4;251;1032;54,7;247;1045;38;3246;14408;13,7;6312;25736;8,6;10718;44589;9,2;16111;72201;6,9;83586;340813;4,6;147536;623612;4;1;670,3;2;268,1;55;12,1;80;8,3;1131;1,4;2133;2,1;5529;3,8;8273;11,8;28713;1,9;209128;2,3;IU 380 3989;; +20200035;VENDREDI;01/05/2020;;30/08/2020;47;20;19;45;38;2;6;-19-20-38-45-47-;-2-6-;0;0;0;2;2;232361,2;1;4;27153,3;5;18;1879,4;83;401;155,4;233;1114;59,1;157;856;54;3869;17825;12,9;4083;19899;12,9;8598;43289;11,1;24452;103637;5,6;65937;307608;5,9;138003;664701;4,4;1;715,6;3;190,8;31;23;79;9;1226;1,4;1410;3,4;8077;2,7;13732;7,6;22215;2,6;229306;2,2;VG 211 9841;; +20200034;MARDI ;28/04/2020;;27/08/2020;32;28;26;15;23;1;5;-15-23-26-28-32-;-1-5-;0;0;0;0;2;177507,9;0;3;27657,6;6;20;1292,2;98;418;113,8;211;948;53;194;922;38,3;3083;13442;13,1;4426;19829;9,9;9514;41941;8,7;15797;68361;6,5;60583;272611;5,1;133879;588301;3,8;0;0;2;528,1;32;18,3;68;8,6;1035;1,4;1436;2,7;5367;3,4;8524;10;20340;2,3;172505;2,4;PL 337 1119;; +20200033;VENDREDI;24/04/2020;;23/08/2020;28;5;1;21;19;7;4;-1-5-19-21-28-;-4-7-;0;0;0;0;2;221796,2;1;11;9424,9;16;58;556,7;275;838;70,9;600;1956;32,1;464;1666;26,5;6791;24446;9;9026;32242;7,6;16630;63734;7,2;30010;112741;4,9;104992;397681;4,4;194001;789050;3,5;0;0;6;219,5;98;7,4;188;3,8;2218;0,8;3045;1,6;9586;2,4;13989;7,6;34973;1,7;219530;2,4;MP 768 7600;; +20200032;MARDI ;21/04/2020;;20/08/2020;11;10;23;14;28;6;8;-10-11-14-23-28-;-6-8-;0;0;0;0;0;0;3;9;44642,1;14;36;658,5;206;630;69,3;340;1220;37,8;345;1302;24,9;4087;15103;10,7;6360;23666;7,6;13598;49650;6,7;17802;69348;5,8;73917;289915;4,4;153393;613372;3,3;0;0;4;251,2;66;8,4;115;4,8;1387;1;2165;1,7;6227;2,8;8568;9,5;25245;1,8;156896;2,5;DZ 292 6112;; +20200031;VENDREDI;17/04/2020;;16/08/2020;37;32;45;28;16;1;11;-16-28-32-37-45-;-1-11-;0;1;67188313;0;2;248682,9;1;7;16606;5;25;1448,2;97;424;157,3;169;846;83,3;329;1382;35,8;2625;13216;18,7;4451;20091;13,7;12204;54040;9,5;14870;72658;8,5;61970;289473;6,7;171474;765397;4,1;0;0;1;1460,9;33;24,5;59;13,7;923;2,1;1508;3,6;5361;4,7;9735;12,1;20950;3,1;217281;2,7;EP 941 6404;; +20200030;MARDI ;14/04/2020;;13/08/2020;40;34;29;6;35;11;4;-6-29-34-35-40-;-4-11-;0;0;0;0;1;357319,7;0;0;0;1;11;9956,6;52;249;192,4;96;580;87,3;140;630;56,5;1764;9521;18,6;2548;12558;15,8;6130;29444;12,5;11053;56431;7,9;42425;205136;6,8;105120;488245;4,6;0;0;0;0;16;101,2;29;19,9;613;2,3;913;4,2;3877;4,7;7147;11,8;14584;3,2;164942;2,5;RY 887 0333;; +20200029;VENDREDI;10/04/2020;;09/08/2020;33;17;3;39;10;1;4;-3-10-17-33-39-;-1-4-;0;0;0;0;0;0;0;6;97603,5;3;27;1279,8;113;621;102,5;247;1122;59,9;340;1499;31,5;3718;15836;14,9;6031;25977;10,1;14268;61580;7,9;19161;81248;7,3;86858;361603;5,1;198160;837155;3,6;0;0;1;1425,2;35;22,6;92;8,6;1226;1,6;1910;2,7;6197;4;10184;11,3;28324;2,3;222943;2,5;CK 238 9831;; +20200028;MARDI ;07/04/2020;;07/06/2020;33;14;4;48;5;7;12;-4-5-14-33-48-;-7-12-;0;0;0;1;3;115795,1;0;2;40594,8;1;8;3161;62;293;158,9;134;580;84,9;155;722;47,9;1962;8985;19,2;3269;14646;13,1;7709;35661;10;10466;48459;8,9;53752;232864;5,8;124938;557176;3,9;1;560,7;0;0;30;33,6;47;11,9;692;2;1168;3,2;3719;4,7;6097;13,4;18784;2,4;159077;2,5;IB 144 9020;; +20200027;VENDREDI;03/04/2020;;03/06/2020;50;16;34;19;46;2;6;-16-19-34-46-50-;-2-6-;0;0;0;0;1;436604,6;0;1;102041,7;10;27;1177,1;83;395;148,2;222;957;64,6;154;862;50,4;3649;16390;13,2;3601;18322;13,2;7629;39155;11,5;22408;94506;5,7;62074;288016;5,9;128638;615452;4,5;0;0;3;415,6;27;25,6;84;8,2;1179;1,4;1142;4;7313;2,9;13574;7,4;20687;2,7;228328;2,2;ST 896 1391;; +20200026;MARDI ;31/03/2020;;31/05/2020;30;12;17;24;36;9;1;-12-17-24-30-36-;-1-9-;0;0;0;0;0;0;1;8;48887,1;4;20;1153,8;91;447;95,1;221;883;50,8;233;1017;31;2860;12204;12,9;3944;17399;10,1;9196;40152;8,1;14397;62225;6,3;51907;231617;5,4;120194;537703;3,7;0;0;2;442,1;29;16,9;77;6,3;949;1,2;1377;2,3;4665;3,3;7601;9,4;17633;2,2;135411;2,6;GO 523 3393;; +20200025;VENDREDI;27/03/2020;;27/05/2020;14;41;27;46;11;3;2;-11-14-27-41-46-;-2-3-;0;0;0;0;3;134054,9;1;4;23498,1;3;11;2661,4;102;515;104,7;161;811;70,2;202;922;43,4;2748;12637;15,8;4760;20959;10,6;8895;40640;10,2;14485;68974;7,3;71085;309679;5,1;131050;594393;4,3;0;0;3;376,1;31;20,2;45;13,9;886;1,7;1528;2,7;4730;4,1;7953;11,5;23011;2,2;211511;2,1;EQ 601 1758;; +20200024;MARDI ;24/03/2020;;24/05/2020;33;12;7;26;30;11;10;-7-12-26-30-33-;-10-11-;0;0;0;0;0;0;1;7;50422,7;1;14;1487,6;87;342;112,1;125;517;78,4;252;984;28,9;1458;7338;19,4;2919;13096;12,1;10802;41534;7,1;7204;38881;9,2;39585;181198;6,2;137600;559969;3,2;0;0;1;817,7;37;12,2;42;10,8;487;2,3;1044;2,9;2510;5,7;3783;17,5;13990;2,6;102953;3,2;IW 207 3107;; +20200023;VENDREDI;20/03/2020;;20/05/2020;40;9;20;34;3;6;12;-3-9-20-34-40-;-6-12-;1;1;17000000;0;1;410858,5;1;5;19204,8;3;17;1759,3;87;372;148,1;117;646;90,1;221;1026;39,8;2050;10259;19,9;3598;17906;12,7;10256;48304;8,7;10847;54381;9,4;53601;257346;6,3;154505;705250;3,7;0;0;0;0;28;61,8;47;13,1;776;1,9;1197;3,4;4057;4,8;6852;13,1;18119;2,8;157956;2,8;DQ 754 7123;; +20200022;MARDI ;17/03/2020;;17/05/2020;5;7;16;8;20;12;2;-5-7-8-16-20-;-2-12-;0;1;63600033;0;1;404531,9;4;10;9454,5;7;20;1472,4;198;663;81,8;211;909;63;543;1871;21,5;2651;11361;17,7;6485;24878;9;17750;65329;6,4;11916;56137;9;73342;308690;5,1;198269;802072;3,2;0;0;3;387,4;83;7,7;59;10,9;984;1,6;2382;1,8;4370;4,6;6635;14,2;25916;2;158797;2,9;EY 698 5252;; +20200021;VENDREDI;13/03/2020;;13/05/2020;21;3;11;36;27;10;9;-3-11-21-27-36-;-9-10-;0;0;0;1;5;159677,5;3;10;18659,6;8;28;2075,7;269;1050;101,9;363;1458;77,6;605;2496;31,8;5001;21469;18,5;11144;43876;10,1;27439;107944;7,6;25756;108210;9,2;150457;586799;5,3;371375;1433503;3,5;0;0;3;807,9;91;14,7;102;13,2;1637;2;3525;2,5;8319;5;13443;14,6;47778;2,3;369298;2,6;OZ 957 7619;; +20200020;MARDI ;10/03/2020;;10/05/2020;23;43;30;11;37;5;12;-11-23-30-37-43-;-5-12-;0;0;0;1;4;132535,6;5;11;11263,9;10;30;1286,4;125;618;115;185;978;76,8;299;1393;37,9;2433;13649;19,3;4725;25024;11,7;12233;60287;9;12982;72480;9,1;66613;352601;5,9;174611;865052;3,8;1;756,7;3;201,8;39;19,4;63;12;863;2,1;1614;3,1;4530;5,2;8031;13,7;23095;2,7;205242;2,6;PZ 739 9603;; +20200019;VENDREDI;06/03/2020;;06/05/2020;15;43;38;45;46;11;1;-15-38-43-45-46-;-1-11-;0;0;0;0;3;233153,1;0;2;81737,5;4;24;2121,6;69;454;206,6;217;1195;82,9;234;1239;56,2;3214;17365;20;4417;24578;15,8;11602;61429;11,7;18405;98569;8,8;69935;375415;7,3;186511;960898;4,6;0;0;1;1821,9;19;53,2;77;13,1;1135;2,2;1523;4,4;6493;4,9;12454;11,8;24045;3,4;281164;2,6;MU 910 9419;; +20200018;MARDI ;03/03/2020;;03/05/2020;27;44;4;14;45;10;1;-4-14-27-44-45-;-1-10-;0;0;0;1;2;250722,9;3;8;14649,5;10;31;1177,5;105;515;130,5;205;942;75,4;239;1323;37,7;2573;12916;19,3;4267;21927;12,7;11853;60320;8,5;13470;69504;9;62072;311126;6,3;169129;849665;3,7;1;746,3;4;149,2;38;19,6;64;11,6;988;1,8;1567;3,1;5025;4,6;8320;13;22599;2,7;196089;2,7;FW 423 7925;; +20200017;VENDREDI;28/02/2020;;29/04/2020;8;11;23;20;22;4;3;-8-11-20-22-23-;-3-4-;0;1;67084644;1;4;197637,5;1;6;30794;18;86;669,1;350;1460;72,6;494;2458;45,5;644;2778;28,3;6382;31146;12,6;12964;56318;7,7;24659;110228;7,4;28327;142449;6,9;156684;718242;4,3;299898;1408162;3,5;0;0;5;419,1;116;10;164;7;2091;1,3;4363;1,7;9675;3,7;14037;12,1;53122;1,8;339607;2,4;JI 004 5663;; +20200016;MARDI ;25/02/2020;;26/04/2020;4;42;18;27;1;6;4;-1-4-18-27-42-;-4-6-;0;0;0;0;2;283293,4;0;4;33105,1;10;41;1005,9;151;698;108,8;449;1862;43,1;352;1551;36,3;6194;25527;11;6922;31969;9,8;13918;66345;8,8;28941;126041;5,6;92196;437820;5,1;190199;928177;3,8;0;0;5;312,1;67;12,9;157;5,5;2348;0,9;2627;2,2;10602;2,5;15607;8,1;34304;2;246971;2,5;OQ 161 5938;; +20200015;VENDREDI;21/02/2020;;22/04/2020;32;23;30;45;7;5;9;-7-23-30-32-45-;-5-9-;0;0;0;1;4;183306,6;2;6;28561,1;14;64;834;192;1075;91,4;395;2748;37,8;401;1893;38,5;6384;35431;10,3;8177;41353;9,8;17339;83014;9,1;34181;180129;5;114601;578223;5;240724;1158067;4;0;0;5;402,8;60;18,6;131;8,5;2083;1,3;2864;2,6;11264;3,1;18536;8,8;39409;2,3;337500;2,4;RE 501 9639;; +20200014;MARDI ;18/02/2020;;19/04/2020;34;32;11;38;47;10;2;-11-32-34-38-47-;-2-10-;0;0;0;0;2;262027,4;1;3;40826,7;3;22;1734;75;432;162,6;237;1051;70,6;194;1084;48,1;3073;14720;17,7;3916;21333;13,6;9325;51032;10,6;17320;81867;8;59434;313390;6,5;141417;752328;4,4;0;0;1;1483,5;24;34,3;80;10,3;1170;1,7;1488;3,7;6728;3,8;12221;9,8;22254;3;248114;2,4;NA 431 6997;; +20200013;VENDREDI;14/02/2020;;15/04/2020;5;35;20;41;49;10;6;-5-20-35-41-49-;-6-10-;0;0;0;1;3;244264,1;1;6;28544,2;3;22;2424,7;100;622;157,9;277;1413;73,5;306;1573;46,4;4027;20251;18;5845;27721;14,6;14149;67779;11,1;24351;114200;8;95066;433629;6,6;232793;1067373;4,3;0;0;0;0;35;95,8;117;10,2;1408;2,1;2025;3,9;8757;4,3;15773;11;33374;2,9;354316;2,4;MO 198 0573;; +20200012;MARDI ;11/02/2020;;12/04/2020;43;26;24;32;46;6;10;-24-26-32-43-46-;-6-10-;0;1;17000000;1;5;105507,3;0;5;24658,7;3;22;1745,5;93;441;160,4;210;1016;73,6;232;1073;48,9;2822;15248;17,2;4476;21754;13,4;9566;49845;10,9;15585;82255;8;63583;319038;6,5;142757;745826;4,4;0;0;0;0;42;53,1;79;10;991;2;1582;3,3;5586;4,4;10009;11,6;22658;2,9;238130;2,4;VQ 986 5562;; +20200011;VENDREDI;07/02/2020;;08/04/2020;17;15;9;25;40;9;3;-9-15-17-25-40-;-3-9-;0;1;130000000;3;10;130443,5;1;21;14517,5;33;125;759,6;351;1753;99,7;1122;4818;38,3;758;3470;37,4;15264;64186;10,1;17574;78373;9,2;35075;154867;8,7;74755;316390;5,1;240147;1051780;4,8;491544;2131657;3,8;2;996,3;8;199,2;91;21,8;347;5,7;4731;1;5490;2,4;23186;2,7;34382;8,4;74820;2,1;577805;2,5;PX 206 8108;; +20200010;MARDI ;04/02/2020;;05/04/2020;35;21;33;23;47;6;7;-21-23-33-35-47-;-6-7-;0;0;0;0;4;155596,4;0;9;16162,4;4;47;963,9;97;659;126,6;278;1573;56;222;1461;42,4;3826;22081;14;5207;32479;10,6;9613;61757;10,4;20273;116314;6,7;80717;473901;5,1;144653;910511;4,3;0;0;1;1350,4;34;22;80;9,3;1192;1,5;1706;2,9;6533;3,6;11927;9,1;25985;2,3;261869;2;JF 573 7673;; diff --git a/skills-lock.json b/skills-lock.json index deaf0b3..921621f 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -1,11 +1,29 @@ { "version": 1, "skills": { + "domain-modeling": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/engineering/domain-modeling/SKILL.md", + "computedHash": "363cb0f53b0b431e7c00086ad1f823500b7e1b70b5616ee969c979f0934e9e6e" + }, + "grill-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/grill-me/SKILL.md", + "computedHash": "f361db4e15e6bfd562a9282b1dccda513910a50061f9e838ce017be9c69dde3f" + }, "grill-with-docs": { "source": "mattpocock/skills", "sourceType": "github", "skillPath": "skills/engineering/grill-with-docs/SKILL.md", "computedHash": "9c460cbd94fd3c63cdef967dbdb6e66ca687103cdc380cd37834e4d10b738f78" + }, + "grilling": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/grilling/SKILL.md", + "computedHash": "4ebdd12fe61ff3abf20cff6683740e2b9b3739454be0a3f4a928a1c4d07d6b34" } } } diff --git a/src/App.tsx b/src/App.tsx index cdb9eb2..c662881 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,14 @@ -function App() { - return null -} +import { QueryClientProvider } from '@tanstack/react-query'; +import { RouterProvider } from 'react-router'; +import { queryClient } from './app/queryClient.ts'; +import { router } from './app/router.tsx'; -export default App +const App = () => { + return ( + + + + ); +}; + +export default App; diff --git a/src/app/AppLayout.module.scss b/src/app/AppLayout.module.scss new file mode 100644 index 0000000..5ee1284 --- /dev/null +++ b/src/app/AppLayout.module.scss @@ -0,0 +1,72 @@ +.layout { + display: flex; + flex-direction: column; + flex: 1; +} + +.nav { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-5); + padding: var(--space-4) var(--space-6); + border-bottom: 1px solid var(--border); + background: var(--surface); + position: sticky; + top: 0; + z-index: 10; +} + +.brand { + font-family: var(--mono); + font-size: var(--text-sm); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--accent-primary); +} + +.navLinks { + display: flex; + gap: var(--space-2); +} + +.navLink { + color: var(--text-muted); + text-decoration: none; + font-size: var(--text-sm); + font-weight: 600; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-sm); + transition: color 0.15s ease, background 0.15s ease; + + &:hover { + color: var(--text); + background: var(--surface-raised); + } +} + +.navLinkActive { + color: var(--bg); + background: var(--accent-primary); + + &:hover { + color: var(--bg); + background: var(--accent-primary); + opacity: 0.9; + } +} + +.content { + flex: 1; + padding: var(--space-6) var(--space-7); + max-width: 100%; +} + +.disclaimer { + padding: var(--space-4) var(--space-7); + border-top: 1px solid var(--border); + font-size: var(--text-xs); + color: var(--text-muted); + text-align: center; +} diff --git a/src/app/AppLayout.tsx b/src/app/AppLayout.tsx new file mode 100644 index 0000000..0260d66 --- /dev/null +++ b/src/app/AppLayout.tsx @@ -0,0 +1,41 @@ +import { NavLink, Outlet } from 'react-router'; +import styles from './AppLayout.module.scss'; + +const NAV_ITEMS = [ + { to: '/evaluation', label: 'Évaluation' }, + { to: '/draws', label: 'Tirages' }, + { to: '/geometry', label: 'Géométrie' }, + { to: '/laboratory', label: 'Laboratoire' }, + { to: '/discovery', label: 'Discovery' }, +]; + +const AppLayout = () => { + return ( +
+ +
+ +
+
+ Toutes les combinaisons EuroMillions valides ont la même probabilité théorique lors d'un + tirage équitable. Cette application analyse des structures historiques et des hypothèses + expérimentales ; elle ne prédit aucun tirage et ne garantit aucun gain. +
+
+ ); +}; + +export default AppLayout; diff --git a/src/app/queryClient.ts b/src/app/queryClient.ts new file mode 100644 index 0000000..6d46de5 --- /dev/null +++ b/src/app/queryClient.ts @@ -0,0 +1,3 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient(); diff --git a/src/app/router.tsx b/src/app/router.tsx new file mode 100644 index 0000000..1b98480 --- /dev/null +++ b/src/app/router.tsx @@ -0,0 +1,27 @@ +import { Navigate, createBrowserRouter } from 'react-router'; +import AppLayout from './AppLayout.tsx'; +import EvaluationPage from '../pages/evaluation/EvaluationPage.tsx'; +import DrawsPage from '../pages/draws/DrawsPage.tsx'; +import GeometryPage from '../pages/geometry/GeometryPage.tsx'; +import LaboratoryPage from '../pages/laboratory/LaboratoryPage.tsx'; +import DiscoveryPage from '../pages/discovery/DiscoveryPage.tsx'; +import SpikePage from '../pages/spike/SpikePage.tsx'; + +export const router = createBrowserRouter( + [ + { + path: '/', + element: , + children: [ + { index: true, element: }, + { path: 'evaluation', element: }, + { path: 'draws', element: }, + { path: 'geometry', element: }, + { path: 'laboratory', element: }, + { path: 'discovery', element: }, + { path: 'spike', element: }, + ], + }, + ], + { basename: import.meta.env.VITE_BASE_PATH || '/' }, +); diff --git a/src/application/buildSpatialEmbeddings.test.ts b/src/application/buildSpatialEmbeddings.test.ts new file mode 100644 index 0000000..8cc78a5 --- /dev/null +++ b/src/application/buildSpatialEmbeddings.test.ts @@ -0,0 +1,25 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../infrastructure/csv/parseFdjCsv.ts'; +import { buildSpatialEmbeddings } from './buildSpatialEmbeddings.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); + +describe('buildSpatialEmbeddings', () => { + it('produces one finite 3D SpatialEmbedding per draw from the real dataset', () => { + const csvText = readFileSync(REAL_CSV_PATH, 'utf8'); + const draws = parseFdjCsv(csvText); + + const embeddings = buildSpatialEmbeddings(draws); + + expect(embeddings).toHaveLength(draws.length); + embeddings.forEach((embedding, index) => { + expect(embedding.drawId).toBe(draws[index].id); + expect(embedding.method).toBe('pca'); + expect(Number.isFinite(embedding.coordinates.x)).toBe(true); + expect(Number.isFinite(embedding.coordinates.y)).toBe(true); + expect(Number.isFinite(embedding.coordinates.z)).toBe(true); + }); + }); +}); diff --git a/src/application/buildSpatialEmbeddings.ts b/src/application/buildSpatialEmbeddings.ts new file mode 100644 index 0000000..5f5237a --- /dev/null +++ b/src/application/buildSpatialEmbeddings.ts @@ -0,0 +1,25 @@ +import { PCA } from 'ml-pca'; +import type { Draw } from '../domain/draw/Draw.ts'; +import { extractFeatures, featureVectorToArray } from '../domain/features/FeatureExtractor.ts'; +import type { SpatialEmbedding } from '../domain/geometry/SpatialEmbedding.ts'; + +export const buildSpatialEmbeddings = (draws: Draw[]): SpatialEmbedding[] => { + const matrix = draws.map((draw) => + featureVectorToArray(extractFeatures({ numbers: draw.numbers, stars: draw.stars })), + ); + + const pca = new PCA(matrix, { center: true, scale: true }); + const projected = pca.predict(matrix, { nComponents: 3 }).to2DArray(); + + return draws.map((draw, index): SpatialEmbedding => { + const [x, y, z] = projected[index]; + return { + drawId: draw.id, + coordinates: { x, y, z }, + method: 'pca', + density: 0, + outlierScore: 0, + nearestNeighbors: [], + }; + }); +}; diff --git a/src/application/discoverStructure.test.ts b/src/application/discoverStructure.test.ts new file mode 100644 index 0000000..da97857 --- /dev/null +++ b/src/application/discoverStructure.test.ts @@ -0,0 +1,60 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../infrastructure/csv/parseFdjCsv.ts'; +import { discoverStructure } from './discoverStructure.ts'; +import type { DiscoveryConfig } from './discoverStructure.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +const CONFIG: DiscoveryConfig = { + kCandidates: [2, 3, 4, 5, 6], + neighborCount: 10, + bootstrapIterations: 15, + seed: 42, +}; + +describe('discoverStructure', () => { + it('produces one real SpatialEmbedding per draw, with actual (non-stubbed) density/outlierScore/clusterId', () => { + const result = discoverStructure(draws, CONFIG); + + expect(result.embeddings).toHaveLength(draws.length); + for (const embedding of result.embeddings) { + expect(embedding.method).toBe('pca'); + expect(embedding.clusterId).toBeDefined(); + expect(embedding.nearestNeighbors).toHaveLength(CONFIG.neighborCount); + expect(Number.isFinite(embedding.density)).toBe(true); + expect(Number.isFinite(embedding.outlierScore)).toBe(true); + } + // Not every point can be the stubbed spike values (density=0, outlierScore=0 for all). + expect(result.embeddings.some((e) => e.density !== 0)).toBe(true); + expect(new Set(result.embeddings.map((e) => e.outlierScore)).size).toBeGreaterThan(1); + }); + + it('produces families covering every draw exactly once, each with a description', () => { + const result = discoverStructure(draws, CONFIG); + + const totalMembers = result.families.reduce((acc, family) => acc + Math.round(family.frequency * draws.length), 0); + expect(totalMembers).toBe(draws.length); + for (const family of result.families) { + expect(family.description.length).toBeGreaterThan(0); + expect(family.stability).toBeGreaterThanOrEqual(0); + expect(family.stability).toBeLessThanOrEqual(1); + } + }); + + it('is reproducible for the same seed and config', () => { + const a = discoverStructure(draws, CONFIG); + const b = discoverStructure(draws, CONFIG); + + expect(a).toEqual(b); + }); + + it('compares real vs synthetic clustering structure and reports a boolean conclusion either way', () => { + const result = discoverStructure(draws, CONFIG); + + expect(typeof result.nullHypothesisComparison.realShowsMoreStructure).toBe('boolean'); + expect(Number.isFinite(result.nullHypothesisComparison.syntheticSilhouetteScore)).toBe(true); + }); +}); diff --git a/src/application/discoverStructure.ts b/src/application/discoverStructure.ts new file mode 100644 index 0000000..53d25df --- /dev/null +++ b/src/application/discoverStructure.ts @@ -0,0 +1,177 @@ +import type { Draw } from '../domain/draw/Draw.ts'; +import type { FeatureKey, FeatureVector } from '../domain/features/FeatureExtractor.ts'; +import { FEATURE_KEYS, extractFeatures } from '../domain/features/FeatureExtractor.ts'; +import type { NormalizationModel } from '../domain/discovery/normalizeFeatures.ts'; +import { applyNormalization, fitNormalization } from '../domain/discovery/normalizeFeatures.ts'; +import type { DimensionalityReduction } from '../domain/discovery/reduceDimensions.ts'; +import { reduceDimensionsPca } from '../domain/discovery/reduceDimensions.ts'; +import type { KMeansCandidateScore, KMeansFitResult } from '../domain/discovery/kmeansClustering.ts'; +import { computeBootstrapStability, fitKMeansWithSilhouette } from '../domain/discovery/kmeansClustering.ts'; +import { computeDensityEvaluations } from '../domain/discovery/densityEvaluation.ts'; +import { generateSyntheticDraws } from '../domain/discovery/generateSyntheticDraws.ts'; +import type { SpatialEmbedding } from '../domain/geometry/SpatialEmbedding.ts'; + +const STABILITY_THRESHOLD = 0.5; +const FAMILY_LABELS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; + +const FEATURE_DESCRIPTIONS: Partial> = { + sum: { high: 'Somme élevée', low: 'Somme faible' }, + range: { high: 'Amplitude large', low: 'Amplitude resserrée' }, + oddCount: { high: 'Majorité de numéros impairs', low: 'Majorité de numéros pairs' }, + aboveThirtyOneCount: { high: 'Plusieurs numéros supérieurs à 31', low: 'Peu de numéros supérieurs à 31' }, + consecutivePairsCount: { high: 'Plusieurs numéros consécutifs', low: "Peu de numéros consécutifs" }, + sameUnitsPairsCount: { high: "Plusieurs numéros de même unité", low: "Peu de répétition d'unité" }, + entropy: { high: 'Répartition étalée sur les dizaines', low: 'Répartition concentrée sur peu de dizaines' }, + clusterSizeMax: { high: 'Numéros concentrés dans une dizaine', low: 'Numéros bien répartis entre les dizaines' }, + starsSum: { high: 'Étoiles à valeur élevée', low: 'Étoiles à valeur faible' }, +}; + +export interface Family { + clusterId: number; + label: string; + description: string; + frequency: number; + stability: number; + isStable: boolean; +} + +export interface DiscoveryConfig { + kCandidates: number[]; + neighborCount: number; + bootstrapIterations: number; + seed: number; + syntheticSampleCount?: number; +} + +export interface NullHypothesisComparison { + realSilhouetteScore: number; + syntheticSilhouetteScore: number; + realShowsMoreStructure: boolean; +} + +export interface DiscoveryResult { + embeddings: SpatialEmbedding[]; + families: Family[]; + normalization: NormalizationModel; + pca: Pick; + clustering: { k: number; silhouetteScore: number; candidateScores: KMeansCandidateScore[] }; + nullHypothesisComparison: NullHypothesisComparison; +} + +const mean = (values: number[]): number => values.reduce((acc, value) => acc + value, 0) / values.length; + +const describeCluster = ( + clusterVectors: FeatureVector[], + overallMeans: Record, + overallStdDevs: Record, +): string[] => + FEATURE_KEYS.filter((key) => key in FEATURE_DESCRIPTIONS && overallStdDevs[key] > 0) + .map((key) => ({ + key, + zScore: (mean(clusterVectors.map((vector) => vector.values[key])) - overallMeans[key]) / overallStdDevs[key], + })) + .sort((a, b) => Math.abs(b.zScore) - Math.abs(a.zScore)) + .slice(0, 3) + .map(({ key, zScore }) => (zScore >= 0 ? FEATURE_DESCRIPTIONS[key]!.high : FEATURE_DESCRIPTIONS[key]!.low)); + +const buildFamilies = ( + vectors: FeatureVector[], + clusteringFit: KMeansFitResult, + stabilityByCluster: Map, + normalization: NormalizationModel, +): Family[] => { + const overallMeans: Record = {}; + for (const key of FEATURE_KEYS) overallMeans[key] = mean(vectors.map((vector) => vector.values[key])); + + const clusterIds = [...new Set(clusteringFit.clusterIds)].sort((a, b) => a - b); + + return clusterIds.map((clusterId, index) => { + const memberVectors = clusteringFit.clusterIds + .map((id, memberIndex) => (id === clusterId ? memberIndex : -1)) + .filter((memberIndex) => memberIndex !== -1) + .map((memberIndex) => vectors[memberIndex]); + + const stability = stabilityByCluster.get(clusterId) ?? 0; + + return { + clusterId, + label: `Famille ${FAMILY_LABELS[index] ?? index}`, + description: describeCluster(memberVectors, overallMeans, normalization.stdDevs).join(', '), + frequency: memberVectors.length / vectors.length, + stability, + isStable: stability >= STABILITY_THRESHOLD, + }; + }); +}; + +/** + * The real DiscoveryModel: normalize -> PCA -> K-Means (+ bootstrap + * stability) -> k-NN density, producing actual SpatialEmbedding[] with + * real density/outlierScore/clusterId/nearestNeighbors - replacing the + * ADR-0002 spike's stubbed 0/0/undefined/[] placeholders. Also runs the + * same pipeline on a synthetic null-hypothesis dataset for comparison. + */ +export const discoverStructure = (draws: Draw[], config: DiscoveryConfig): DiscoveryResult => { + const vectors = draws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); + const normalization = fitNormalization(vectors); + const normalizedRows = applyNormalization(vectors, normalization); + const pca = reduceDimensionsPca(normalizedRows, normalization.featureKeys); + const pcaRows = pca.coordinates.map((c) => [c.x, c.y, c.z]); + + const clusteringFit = fitKMeansWithSilhouette(pcaRows, config.kCandidates, config.seed); + const stabilities = computeBootstrapStability( + pcaRows, + clusteringFit.clusterIds, + clusteringFit.k, + config.bootstrapIterations, + config.seed, + ); + const stabilityByCluster = new Map(stabilities.map((entry) => [entry.clusterId, entry.stability])); + + const densityEvaluations = computeDensityEvaluations(pcaRows, config.neighborCount); + + const embeddings: SpatialEmbedding[] = draws.map((draw, index) => ({ + drawId: draw.id, + coordinates: pca.coordinates[index], + method: 'pca', + clusterId: String(clusteringFit.clusterIds[index]), + density: densityEvaluations[index].densityPercentile, + outlierScore: densityEvaluations[index].outlierScore, + nearestNeighbors: densityEvaluations[index].nearestNeighborIndices.map((neighborIndex, neighborRank) => ({ + drawId: draws[neighborIndex].id, + distance: densityEvaluations[index].nearestNeighborDistances[neighborRank], + })), + })); + + const families = buildFamilies(vectors, clusteringFit, stabilityByCluster, normalization); + + const syntheticDraws = generateSyntheticDraws( + config.syntheticSampleCount ?? draws.length, + config.seed + 1000, + draws[0]?.date ?? '2000-01-01', + ); + const syntheticVectors = syntheticDraws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); + const syntheticNormalization = fitNormalization(syntheticVectors); + const syntheticRows = applyNormalization(syntheticVectors, syntheticNormalization); + const syntheticPcaRows = reduceDimensionsPca(syntheticRows, syntheticNormalization.featureKeys).coordinates.map( + (c) => [c.x, c.y, c.z], + ); + const syntheticClusteringFit = fitKMeansWithSilhouette(syntheticPcaRows, config.kCandidates, config.seed); + + return { + embeddings, + families, + normalization, + pca: { explainedVariance: pca.explainedVariance, featureContributions: pca.featureContributions }, + clustering: { + k: clusteringFit.k, + silhouetteScore: clusteringFit.silhouetteScore, + candidateScores: clusteringFit.candidateScores, + }, + nullHypothesisComparison: { + realSilhouetteScore: clusteringFit.silhouetteScore, + syntheticSilhouetteScore: syntheticClusteringFit.silhouetteScore, + realShowsMoreStructure: clusteringFit.silhouetteScore > syntheticClusteringFit.silhouetteScore, + }, + }; +}; diff --git a/src/application/drawRepository.ts b/src/application/drawRepository.ts new file mode 100644 index 0000000..8355252 --- /dev/null +++ b/src/application/drawRepository.ts @@ -0,0 +1,3 @@ +import { createCsvDrawRepository } from '../infrastructure/repositories/CsvDrawRepository.ts'; + +export const drawRepository = createCsvDrawRepository(); diff --git a/src/application/evaluatedGridRepository.ts b/src/application/evaluatedGridRepository.ts new file mode 100644 index 0000000..48ae0e3 --- /dev/null +++ b/src/application/evaluatedGridRepository.ts @@ -0,0 +1,3 @@ +import { createLocalStorageEvaluatedGridRepository } from '../infrastructure/repositories/LocalStorageEvaluatedGridRepository.ts'; + +export const evaluatedGridRepository = createLocalStorageEvaluatedGridRepository(); diff --git a/src/application/generateVariations.test.ts b/src/application/generateVariations.test.ts new file mode 100644 index 0000000..fec0d2c --- /dev/null +++ b/src/application/generateVariations.test.ts @@ -0,0 +1,51 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../infrastructure/csv/parseFdjCsv.ts'; +import { parseGrid } from '../domain/grid/Grid.ts'; +import { generateVariations } from './generateVariations.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +describe('generateVariations', () => { + it('produces one valid grid per variation kind', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + const variations = generateVariations(grid, history); + + expect(variations.map((v) => v.kind).sort()).toEqual( + ['anti-share', 'balanced', 'structurally-common'].sort(), + ); + for (const variation of variations) { + expect(new Set(variation.grid.numbers).size).toBe(5); + expect(new Set(variation.grid.stars).size).toBe(2); + } + }); + + it('spreads the balanced variation across the five decades', () => { + const grid = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + + const [, balanced] = generateVariations(grid, history); + + const buckets = balanced.grid.numbers.map((n) => Math.min(Math.floor((n - 1) / 10), 4)); + expect(new Set(buckets).size).toBe(5); + }); + + it('avoids <=31 and multiples-of-five numbers in the anti-share variation', () => { + const grid = parseGrid({ numbers: [1, 5, 10, 15, 20], stars: [1, 2] }); + + const [, , antiShare] = generateVariations(grid, history); + + for (const n of antiShare.grid.numbers) { + expect(n).toBeGreaterThan(31); + expect(n % 5).not.toBe(0); + } + }); + + it('is deterministic for the same grid and history', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(generateVariations(grid, history)).toEqual(generateVariations(grid, history)); + }); +}); diff --git a/src/application/generateVariations.ts b/src/application/generateVariations.ts new file mode 100644 index 0000000..313cc87 --- /dev/null +++ b/src/application/generateVariations.ts @@ -0,0 +1,106 @@ +import type { Draw } from '../domain/draw/Draw.ts'; +import { parseGrid } from '../domain/grid/Grid.ts'; +import type { Grid } from '../domain/grid/Grid.ts'; + +export type VariationKind = 'structurally-common' | 'balanced' | 'anti-share'; + +export interface GridVariation { + kind: VariationKind; + grid: Grid; +} + +const NUMBER_MIN = 1; +const NUMBER_MAX = 50; +const STAR_MIN = 1; +const STAR_MAX = 12; + +const decadeBucketOf = (n: number): number => Math.min(Math.floor((n - 1) / 10), 4); + +const countFrequencies = (values: number[], min: number, max: number): Record => { + const frequencies: Record = {}; + for (let value = min; value <= max; value += 1) frequencies[value] = 0; + for (const value of values) frequencies[value] += 1; + return frequencies; +}; + +const pickDistinct = (candidatesByPreference: number[], taken: Set): number => { + const choice = candidatesByPreference.find((candidate) => !taken.has(candidate)); + if (choice === undefined) throw new Error('No distinct candidate available for this variation'); + return choice; +}; + +const range = (min: number, max: number): number[] => + Array.from({ length: max - min + 1 }, (_, i) => min + i); + +const buildStructurallyCommonVariation = (grid: Grid, history: Draw[]): Grid => { + const numberFrequencies = countFrequencies(history.flatMap((draw) => draw.numbers), NUMBER_MIN, NUMBER_MAX); + const starFrequencies = countFrequencies(history.flatMap((draw) => draw.stars), STAR_MIN, STAR_MAX); + + const taken = new Set(); + const numbers = grid.numbers.map((original) => { + const bucket = decadeBucketOf(original); + const bucketMin = bucket * 10 + 1; + const bucketMax = Math.min(bucket * 10 + 10, NUMBER_MAX); + const candidates = range(bucketMin, bucketMax).sort( + (a, b) => numberFrequencies[b] - numberFrequencies[a] || Math.abs(a - original) - Math.abs(b - original), + ); + const chosen = pickDistinct(candidates, taken); + taken.add(chosen); + return chosen; + }); + + const starCandidates = range(STAR_MIN, STAR_MAX).sort( + (a, b) => starFrequencies[b] - starFrequencies[a] || Math.abs(a - grid.stars[0]) - Math.abs(b - grid.stars[0]), + ); + const stars = starCandidates.slice(0, 2); + + return parseGrid({ numbers, stars }); +}; + +const buildBalancedVariation = (grid: Grid): Grid => { + const taken = new Set(); + const numbers = grid.numbers.map((original, index) => { + const bucketMin = index * 10 + 1; + const bucketMax = Math.min(index * 10 + 10, NUMBER_MAX); + const clamped = Math.min(Math.max(original, bucketMin), bucketMax); + const candidates = range(bucketMin, bucketMax).sort((a, b) => Math.abs(a - clamped) - Math.abs(b - clamped)); + const chosen = pickDistinct(candidates, taken); + taken.add(chosen); + return chosen; + }); + + const [firstStar, secondStar] = grid.stars; + const oddCandidates = [1, 3, 5, 7, 9, 11].sort((a, b) => Math.abs(a - firstStar) - Math.abs(b - firstStar)); + const evenCandidates = [2, 4, 6, 8, 10, 12].sort((a, b) => Math.abs(a - secondStar) - Math.abs(b - secondStar)); + const stars = [oddCandidates[0], evenCandidates[0]]; + + return parseGrid({ numbers, stars }); +}; + +const isShareableNumber = (n: number): boolean => n <= 31 || n % 5 === 0; + +const buildAntiShareVariation = (grid: Grid): Grid => { + const taken = new Set(grid.numbers); + const numbers = grid.numbers.map((original) => { + if (!isShareableNumber(original)) return original; + taken.delete(original); + const candidates = range(NUMBER_MIN, NUMBER_MAX) + .filter((n) => !isShareableNumber(n)) + .sort((a, b) => Math.abs(a - original) - Math.abs(b - original)); + const chosen = pickDistinct(candidates, taken); + taken.add(chosen); + return chosen; + }); + + return parseGrid({ numbers, stars: grid.stars }); +}; + +/** + * Descriptive variations only - none is presented as more likely to be drawn. + * Every valid Grid has the same theoretical probability of being drawn. + */ +export const generateVariations = (grid: Grid, history: Draw[]): GridVariation[] => [ + { kind: 'structurally-common', grid: buildStructurallyCommonVariation(grid, history) }, + { kind: 'balanced', grid: buildBalancedVariation(grid) }, + { kind: 'anti-share', grid: buildAntiShareVariation(grid) }, +]; diff --git a/src/application/runExperiment.test.ts b/src/application/runExperiment.test.ts new file mode 100644 index 0000000..900590c --- /dev/null +++ b/src/application/runExperiment.test.ts @@ -0,0 +1,74 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../infrastructure/csv/parseFdjCsv.ts'; +import { TEMPORAL_WINDOWS } from '../domain/scoring/TemporalWindow.ts'; +import type { Strategy } from '../domain/strategy/Strategy.ts'; +import { runExperiment } from './runExperiment.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +const strategyOf = (rules: Strategy['rules'], seed = 42): Strategy => ({ + id: 'test-strategy', + name: 'Test', + rules, + seed, +}); + +describe('runExperiment', () => { + it('produces a fully-populated Experiment with generatedGrids matching the test-range steps', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }, { kind: 'recency' }]); + + const experiment = runExperiment({ + strategy, + draws, + trainRatio: 0.7, + validationRatio: 0.15, + windows: TEMPORAL_WINDOWS, + monteCarloSampleCount: 50, + }); + + expect(experiment.id).toBeTruthy(); + expect(experiment.results.generatedGrids.length).toBe(experiment.results.metrics.stepCount); + expect(experiment.results.baselineComparisons).toHaveLength(5); + expect(experiment.windowComparisons).toHaveLength(TEMPORAL_WINDOWS.length); + expect(experiment.monteCarlo.sampleCount).toBe(50); + expect(typeof experiment.overfitting.isLikelyOverfit).toBe('boolean'); + + for (const generated of experiment.results.generatedGrids) { + expect(generated.geometry.grid).toEqual(generated.grid); + expect(generated.score).toBeGreaterThanOrEqual(0); + expect(generated.score).toBeLessThanOrEqual(5); + } + }); + + it('is reproducible: the same strategy/seed/config gives the same experiment results', () => { + const strategy = strategyOf([{ kind: 'above-31', weight: 2 }], 7); + const config = { strategy, draws, trainRatio: 0.7, validationRatio: 0.15, windows: TEMPORAL_WINDOWS, monteCarloSampleCount: 20 }; + + const a = runExperiment(config); + const b = runExperiment(config); + + expect(a.results).toEqual(b.results); + expect(a.overfitting).toEqual(b.overfitting); + expect(a.monteCarlo).toEqual(b.monteCarlo); + expect(a.windowComparisons).toEqual(b.windowComparisons); + }); + + it('supports a strategy with no rules and still reports a valid ("no advantage detected") result', () => { + const strategy = strategyOf([], 1); + + const experiment = runExperiment({ + strategy, + draws, + trainRatio: 0.7, + validationRatio: 0.15, + windows: ['all'], + monteCarloSampleCount: 30, + }); + + expect(experiment.monteCarlo.strategyPercentile).toBeGreaterThanOrEqual(0); + expect(experiment.monteCarlo.strategyPercentile).toBeLessThanOrEqual(100); + }); +}); diff --git a/src/application/runExperiment.ts b/src/application/runExperiment.ts new file mode 100644 index 0000000..1a7e76e --- /dev/null +++ b/src/application/runExperiment.ts @@ -0,0 +1,132 @@ +import type { Draw } from '../domain/draw/Draw.ts'; +import type { Grid } from '../domain/grid/Grid.ts'; +import type { FeatureVector } from '../domain/features/FeatureExtractor.ts'; +import { extractFeatures } from '../domain/features/FeatureExtractor.ts'; +import type { GeometryDescriptor } from '../domain/geometry/GeometryDescriptor.ts'; +import { buildGeometryDescriptor } from '../domain/geometry/GeometryDescriptor.ts'; +import type { Strategy } from '../domain/strategy/Strategy.ts'; +import { proposeGrid } from '../domain/strategy/proposeGrid.ts'; +import { filterByWindow, latestDrawDate } from '../domain/scoring/TemporalWindow.ts'; +import type { TemporalWindow } from '../domain/scoring/TemporalWindow.ts'; +import type { BaselineComparison } from '../domain/backtest/compareBaselines.ts'; +import { compareBaselines } from '../domain/backtest/compareBaselines.ts'; +import type { OverfittingSignal } from '../domain/backtest/detectOverfitting.ts'; +import { detectOverfitting } from '../domain/backtest/detectOverfitting.ts'; +import type { MonteCarloResult } from '../domain/backtest/runMonteCarloComparison.ts'; +import { runMonteCarloComparison } from '../domain/backtest/runMonteCarloComparison.ts'; +import type { BacktestMetrics, DateRange } from '../domain/backtest/runWalkForwardBacktest.ts'; +import { runWalkForwardBacktest } from '../domain/backtest/runWalkForwardBacktest.ts'; +import type { TrainValidationTestRanges } from '../domain/backtest/splitDateRanges.ts'; +import { splitDateRanges } from '../domain/backtest/splitDateRanges.ts'; + +const DATASET_VERSION = 'fdj-euromillions-2020-2026'; +const MODEL_VERSION = 'izeetok-v2-strategy-1'; + +export interface EvaluatedGrid { + grid: Grid; + date: string; + features: FeatureVector; + geometry: GeometryDescriptor; + score?: number; +} + +export interface ExperimentResult { + generatedGrids: EvaluatedGrid[]; + metrics: BacktestMetrics; + baselineComparisons: BaselineComparison[]; +} + +export interface WindowComparison { + window: TemporalWindow; + metrics: BacktestMetrics; +} + +export interface Experiment { + id: string; + createdAt: string; + datasetVersion: string; + modelVersion: string; + seed: number; + trainRange: DateRange; + validationRange: DateRange; + testRange: DateRange; + windows: TemporalWindow[]; + strategy: Strategy; + results: ExperimentResult; + overfitting: OverfittingSignal; + monteCarlo: MonteCarloResult; + windowComparisons: WindowComparison[]; +} + +export interface RunExperimentConfig { + strategy: Strategy; + draws: Draw[]; + trainRatio: number; + validationRatio: number; + windows: TemporalWindow[]; + monteCarloSampleCount: number; +} + +const rangesFor = ( + window: TemporalWindow, + draws: Draw[], + referenceDate: Date, +): DateRange => { + const windowDraws = filterByWindow(draws, window, referenceDate).sort((a, b) => a.date.localeCompare(b.date)); + const fallback = referenceDate.toISOString().slice(0, 10); + + return windowDraws.length > 0 + ? { start: windowDraws[0].date, end: windowDraws[windowDraws.length - 1].date } + : { start: fallback, end: fallback }; +}; + +export const runExperiment = (config: RunExperimentConfig): Experiment => { + const { strategy, draws, trainRatio, validationRatio, windows, monteCarloSampleCount } = config; + const { train, validation, test }: TrainValidationTestRanges = splitDateRanges(draws, trainRatio, validationRatio); + const propose = (history: Draw[]): Grid => proposeGrid(strategy, history); + + const trainResult = runWalkForwardBacktest(propose, draws, train); + const validationResult = runWalkForwardBacktest(propose, draws, validation); + const testResult = runWalkForwardBacktest(propose, draws, test); + + const overfitting = detectOverfitting(trainResult.metrics, validationResult.metrics, testResult.metrics); + const baselineComparisons = compareBaselines(draws, test, strategy.seed); + const monteCarlo = runMonteCarloComparison( + testResult.metrics.meanMatchedNumbers, + draws, + test, + monteCarloSampleCount, + strategy.seed, + ); + + const generatedGrids: EvaluatedGrid[] = testResult.steps.map((step) => ({ + grid: step.proposedGrid, + date: step.date, + features: extractFeatures(step.proposedGrid), + geometry: buildGeometryDescriptor(step.proposedGrid), + score: step.matchedNumbers, + })); + + const referenceDate = latestDrawDate(draws); + const windowComparisons: WindowComparison[] = windows.map((window) => ({ + window, + metrics: runWalkForwardBacktest(propose, draws, rangesFor(window, draws, referenceDate)).metrics, + })); + + return { + id: crypto.randomUUID(), + createdAt: new Date().toISOString(), + datasetVersion: DATASET_VERSION, + modelVersion: MODEL_VERSION, + seed: strategy.seed, + trainRange: train, + validationRange: validation, + testRange: test, + windows, + strategy, + results: { generatedGrids, metrics: testResult.metrics, baselineComparisons }, + overfitting, + monteCarlo, + windowComparisons, + }; +}; diff --git a/src/application/variationLabels.ts b/src/application/variationLabels.ts new file mode 100644 index 0000000..176821b --- /dev/null +++ b/src/application/variationLabels.ts @@ -0,0 +1,16 @@ +import type { VariationKind } from './generateVariations.ts'; + +export const VARIATION_LABELS: Record = { + 'structurally-common': 'Structurellement courante', + balanced: 'Équilibrée', + 'anti-share': 'Anti-partage', +}; + +export const VARIATION_DESCRIPTIONS: Record = { + 'structurally-common': + 'Reproduit un profil (somme, répartition par dizaine, écarts) proche des tirages historiquement fréquents.', + balanced: + 'Répartit les numéros sur les cinq dizaines et équilibre pairs/impairs, pour une grille aux caractéristiques neutres.', + 'anti-share': + 'Moins de chance de partager un gain, car elle évite les numéros calendaires (≤31) que beaucoup de joueurs choisissent - cela ne change pas vos chances de gagner.', +}; diff --git a/src/components/bubble/Bubble.module.scss b/src/components/bubble/Bubble.module.scss new file mode 100644 index 0000000..00922c7 --- /dev/null +++ b/src/components/bubble/Bubble.module.scss @@ -0,0 +1,29 @@ +.bubble { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.75rem; + height: 2.75rem; + flex-shrink: 0; + border-radius: 50%; + font-family: var(--mono); + font-weight: 700; + font-size: var(--text-base); + border: 1px solid var(--border-strong); + background: var(--surface); + color: var(--text); +} + +.number { + border-color: var(--accent-structure); +} + +.star { + border-color: var(--accent-originality); +} + +.highlight { + border-color: var(--accent-reference); + background: color-mix(in srgb, var(--accent-reference) 20%, var(--surface)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-reference) 40%, transparent); +} diff --git a/src/components/bubble/Bubble.tsx b/src/components/bubble/Bubble.tsx new file mode 100644 index 0000000..d98cc73 --- /dev/null +++ b/src/components/bubble/Bubble.tsx @@ -0,0 +1,22 @@ +import styles from './Bubble.module.scss'; + +export type BubbleVariant = 'number' | 'star' | 'highlight'; + +interface BubbleProps { + value: number; + variant?: BubbleVariant; +} + +const VARIANT_CLASSES: Record = { + number: styles.number, + star: styles.star, + highlight: styles.highlight, +}; + +const Bubble = ({ value, variant = 'number' }: BubbleProps) => ( + + {value} + +); + +export default Bubble; diff --git a/src/components/bubble/NumberChain.module.scss b/src/components/bubble/NumberChain.module.scss new file mode 100644 index 0000000..41943a9 --- /dev/null +++ b/src/components/bubble/NumberChain.module.scss @@ -0,0 +1,29 @@ +.chain { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.15rem; +} + +.step { + display: flex; + align-items: center; +} + +.arrow { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 var(--space-2); + color: var(--text-muted); +} + +.gapValue { + font-family: var(--mono); + font-size: var(--text-xs); +} + +.arrowLine { + font-size: 1.1rem; + line-height: 1; +} diff --git a/src/components/bubble/NumberChain.test.tsx b/src/components/bubble/NumberChain.test.tsx new file mode 100644 index 0000000..7619728 --- /dev/null +++ b/src/components/bubble/NumberChain.test.tsx @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import NumberChain from './NumberChain.tsx'; + +describe('NumberChain', () => { + it('renders one bubble per number and one gap arrow between each pair', () => { + render(); + + expect(screen.getAllByTestId('bubble').map((el) => el.textContent)).toEqual(['26', '29', '35', '38', '47']); + }); + + it('labels each arrow with the correct gap', () => { + const { container } = render(); + + const gaps = [...container.querySelectorAll('[class*="gapValue"]')].map((el) => el.textContent); + expect(gaps).toEqual(['3', '6', '3', '9']); + }); +}); diff --git a/src/components/bubble/NumberChain.tsx b/src/components/bubble/NumberChain.tsx new file mode 100644 index 0000000..e7f9030 --- /dev/null +++ b/src/components/bubble/NumberChain.tsx @@ -0,0 +1,27 @@ +import type { BubbleVariant } from './Bubble.tsx'; +import Bubble from './Bubble.tsx'; +import styles from './NumberChain.module.scss'; + +interface NumberChainProps { + numbers: readonly number[]; + variant?: BubbleVariant; +} + +/** Numbers as Bubbles linked by arrows labelled with the gap between them. */ +const NumberChain = ({ numbers, variant = 'number' }: NumberChainProps) => ( +
+ {numbers.map((value, index) => ( + + {index > 0 && ( + + )} + + + ))} +
+); + +export default NumberChain; diff --git a/src/components/chartLegend/ChartLegend.module.scss b/src/components/chartLegend/ChartLegend.module.scss new file mode 100644 index 0000000..b77289e --- /dev/null +++ b/src/components/chartLegend/ChartLegend.module.scss @@ -0,0 +1,23 @@ +.legend { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + list-style: none; + margin: 0; + padding: 0; + font-size: var(--text-xs); + color: var(--text-muted); +} + +.legendItem { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.swatch { + width: 0.65rem; + height: 0.65rem; + border-radius: 50%; + flex-shrink: 0; +} diff --git a/src/components/chartLegend/ChartLegend.tsx b/src/components/chartLegend/ChartLegend.tsx new file mode 100644 index 0000000..94053a5 --- /dev/null +++ b/src/components/chartLegend/ChartLegend.tsx @@ -0,0 +1,23 @@ +import styles from './ChartLegend.module.scss'; + +interface ChartLegendItem { + color: string; + label: string; +} + +interface ChartLegendProps { + items: ChartLegendItem[]; +} + +const ChartLegend = ({ items }: ChartLegendProps) => ( +
    + {items.map((item) => ( +
  • +
  • + ))} +
+); + +export default ChartLegend; diff --git a/src/components/gridInput/GridInputForm.module.scss b/src/components/gridInput/GridInputForm.module.scss new file mode 100644 index 0000000..65919ba --- /dev/null +++ b/src/components/gridInput/GridInputForm.module.scss @@ -0,0 +1,51 @@ +.formWrapper { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.form { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: var(--space-5); +} + +.numberInput { + width: 3rem; + height: 3rem; + padding: 0; + text-align: center; + font-family: var(--mono); + font-size: var(--text-lg); + font-weight: 600; + margin-right: var(--space-2); + transition: background 0.15s ease, border-color 0.15s ease; +} + +.starInput { + border-radius: 50%; + border-color: var(--accent-originality); +} + +// !important: these are state overrides that must win regardless of which +// other input-styling class (e.g. .starInput's own border-color) is combined +// with them - and combined-class cascade order isn't reliably source order +// once Vite's dev-time CSS module injection is involved. +.inputValid { + background: color-mix(in srgb, var(--accent-confidence) 25%, var(--surface)) !important; + border-color: var(--accent-confidence) !important; +} + +.inputInvalid { + background: color-mix(in srgb, var(--danger) 25%, var(--surface)) !important; + border-color: var(--danger) !important; +} + +.error { + color: var(--danger); + background: var(--danger-surface); + border-radius: var(--radius-sm); + padding: var(--space-3) var(--space-4); + display: inline-block; +} diff --git a/src/components/gridInput/GridInputForm.tsx b/src/components/gridInput/GridInputForm.tsx new file mode 100644 index 0000000..38ac0d9 --- /dev/null +++ b/src/components/gridInput/GridInputForm.tsx @@ -0,0 +1,169 @@ +import { useRef, useState } from 'react'; +import type { ChangeEvent, KeyboardEvent } from 'react'; +import type { Grid } from '../../domain/grid/Grid.ts'; +import { parseGrid } from '../../domain/grid/Grid.ts'; +import styles from './GridInputForm.module.scss'; + +const NUMBER_MIN = 1; +const NUMBER_MAX = 50; +const STAR_MIN = 1; +const STAR_MAX = 12; + +const EMPTY_NUMBERS = ['', '', '', '', '']; +const EMPTY_STARS = ['', '']; + +const onlyDigits = (value: string): string => value.replace(/\D/g, '').slice(0, 2); + +// A value repeated at an earlier index makes this (later) field invalid +// too, even if it's otherwise in range - a Grid needs 5 distinct numbers +// and 2 distinct stars, so only the *first* occurrence of a value can be +// valid. +const validityClass = (values: string[], index: number, min: number, max: number): string => { + const value = values[index]; + if (value === '') return ''; + if (values.slice(0, index).includes(value)) return styles.inputInvalid; + const parsed = Number(value); + return parsed >= min && parsed <= max ? styles.inputValid : styles.inputInvalid; +}; + +interface GridInputFormProps { + onSubmit: (grid: Grid) => void; + submitLabel: string; + submitButtonTestId?: string; + initialGrid?: Grid | null; +} + +const GridInputForm = ({ + onSubmit, + submitLabel, + submitButtonTestId = 'evaluate-button', + initialGrid = null, +}: GridInputFormProps) => { + const [numberInputs, setNumberInputs] = useState( + initialGrid ? initialGrid.numbers.map(String) : EMPTY_NUMBERS, + ); + const [starInputs, setStarInputs] = useState( + initialGrid ? initialGrid.stars.map(String) : EMPTY_STARS, + ); + const [error, setError] = useState(null); + + const numberFieldRefs = useRef<(HTMLInputElement | null)[]>([]); + const starFieldRefs = useRef<(HTMLInputElement | null)[]>([]); + const submitButtonRef = useRef(null); + + const handleSubmit = () => { + const numbers = numberInputs.map(Number); + const stars = starInputs.map(Number); + + if ([...numbers, ...stars].some((n) => Number.isNaN(n))) { + setError('Merci de remplir les 5 numéros et les 2 étoiles.'); + return; + } + + try { + const grid = parseGrid({ numbers, stars }); + setError(null); + onSubmit(grid); + } catch { + setError('Grille invalide : 5 numéros distincts entre 1 et 50, 2 étoiles distinctes entre 1 et 12.'); + } + }; + + // Lottery-ball-style entry: plain digit typing, auto-advance once a field + // is full, backspace on an empty field jumps back - avoids the classic + // type="number" pitfalls (spinner arrows, scroll-wheel changes value). + const createDigitFieldHandlers = ( + values: string[], + setValues: (updater: (previous: string[]) => string[]) => void, + refs: (HTMLInputElement | null)[], + nextFieldRef: HTMLElement | null, + ) => ({ + onChange: (index: number) => (event: ChangeEvent) => { + const digits = onlyDigits(event.target.value); + setValues((previous) => previous.map((current, i) => (i === index ? digits : current))); + if (digits.length === 2) { + (refs[index + 1] ?? nextFieldRef)?.focus(); + } + }, + onKeyDown: (index: number) => (event: KeyboardEvent) => { + if (event.key === 'Backspace' && values[index] === '' && index > 0) { + refs[index - 1]?.focus(); + } + }, + }); + + const numberHandlers = createDigitFieldHandlers( + numberInputs, + (updater) => setNumberInputs(updater), + numberFieldRefs.current, + starFieldRefs.current[0] ?? null, + ); + const starHandlers = createDigitFieldHandlers( + starInputs, + (updater) => setStarInputs(updater), + starFieldRefs.current, + submitButtonRef.current, + ); + + return ( +
+
+
+ Numéros (1-50) + {numberInputs.map((value, index) => ( + { + numberFieldRefs.current[index] = element; + }} + className={`${styles.numberInput} ${validityClass(numberInputs, index, NUMBER_MIN, NUMBER_MAX)}`} + type="text" + inputMode="numeric" + pattern="[0-9]*" + maxLength={2} + autoComplete="off" + value={value} + onChange={numberHandlers.onChange(index)} + onKeyDown={numberHandlers.onKeyDown(index)} + data-testid={`number-input-${index}`} + aria-label={`Numéro ${index + 1}`} + /> + ))} +
+
+ Étoiles (1-12) + {starInputs.map((value, index) => ( + { + starFieldRefs.current[index] = element; + }} + className={`${styles.numberInput} ${styles.starInput} ${validityClass(starInputs, index, STAR_MIN, STAR_MAX)}`} + type="text" + inputMode="numeric" + pattern="[0-9]*" + maxLength={2} + autoComplete="off" + value={value} + onChange={starHandlers.onChange(index)} + onKeyDown={starHandlers.onKeyDown(index)} + data-testid={`star-input-${index}`} + aria-label={`Étoile ${index + 1}`} + /> + ))} +
+ +
+ + {error && ( +

+ {error} +

+ )} +
+ ); +}; + +export default GridInputForm; diff --git a/src/domain/backtest/baselines.test.ts b/src/domain/backtest/baselines.test.ts new file mode 100644 index 0000000..5056605 --- /dev/null +++ b/src/domain/backtest/baselines.test.ts @@ -0,0 +1,39 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { BASELINE_KINDS, proposeBaselineGrid } from './baselines.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +describe('proposeBaselineGrid', () => { + it('produces a valid Grid for every baseline kind', () => { + for (const kind of BASELINE_KINDS) { + const grid = proposeBaselineGrid(kind, history, 1); + expect(new Set(grid.numbers).size).toBe(5); + expect(new Set(grid.stars).size).toBe(2); + } + }); + + it('fixed-grid always returns the same grid regardless of history or seed', () => { + const a = proposeBaselineGrid('fixed-grid', history, 1); + const b = proposeBaselineGrid('fixed-grid', [], 999); + + expect(a).toEqual(b); + }); + + it('uniform-random ignores history entirely', () => { + const withHistory = proposeBaselineGrid('uniform-random', history, 7); + const withoutHistory = proposeBaselineGrid('uniform-random', [], 7); + + expect(withHistory).toEqual(withoutHistory); + }); + + it('frequent-numbers and rare-numbers disagree on a real history', () => { + const frequent = proposeBaselineGrid('frequent-numbers', history, 1); + const rare = proposeBaselineGrid('rare-numbers', history, 1); + + expect(frequent).not.toEqual(rare); + }); +}); diff --git a/src/domain/backtest/baselines.ts b/src/domain/backtest/baselines.ts new file mode 100644 index 0000000..7821dcc --- /dev/null +++ b/src/domain/backtest/baselines.ts @@ -0,0 +1,73 @@ +import type { Draw } from '../draw/Draw.ts'; +import type { Grid } from '../grid/Grid.ts'; +import { parseGrid } from '../grid/Grid.ts'; +import { proposeGrid } from '../strategy/proposeGrid.ts'; + +export type BaselineKind = 'uniform-random' | 'fixed-grid' | 'frequent-numbers' | 'rare-numbers' | 'geometric-no-temporal'; + +export const BASELINE_KINDS: BaselineKind[] = [ + 'uniform-random', + 'fixed-grid', + 'frequent-numbers', + 'rare-numbers', + 'geometric-no-temporal', +]; + +export const BASELINE_LABELS: Record = { + 'uniform-random': 'Sélection uniforme aléatoire', + 'fixed-grid': 'Grille fixe', + 'frequent-numbers': 'Numéros historiquement fréquents', + 'rare-numbers': 'Numéros historiquement rares', + 'geometric-no-temporal': 'Modèle géométrique sans composante temporelle', +}; + +// Arbitrary, documented reference point - not chosen for any structural property. +const FIXED_GRID: Grid = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + +/** + * Every baseline but fixed-grid is itself just a Strategy with a specific + * rule set - reusing proposeGrid keeps the baselines honest (same code + * path a user's own strategy would go through) instead of a parallel + * bespoke implementation for each one. + */ +export const proposeBaselineGrid = (kind: BaselineKind, history: Draw[], seed: number): Grid => { + switch (kind) { + case 'uniform-random': + return proposeGrid({ id: `baseline-${kind}`, name: BASELINE_LABELS[kind], rules: [], seed }, history); + case 'fixed-grid': + return FIXED_GRID; + case 'frequent-numbers': + return proposeGrid( + { + id: `baseline-${kind}`, + name: BASELINE_LABELS[kind], + rules: [{ kind: 'number-frequency' }, { kind: 'star-frequency' }], + seed, + }, + history, + ); + case 'rare-numbers': + return proposeGrid( + { + id: `baseline-${kind}`, + name: BASELINE_LABELS[kind], + rules: [ + { kind: 'number-frequency', weight: -1 }, + { kind: 'star-frequency', weight: -1 }, + ], + seed, + }, + history, + ); + case 'geometric-no-temporal': + return proposeGrid( + { + id: `baseline-${kind}`, + name: BASELINE_LABELS[kind], + rules: [{ kind: 'number-frequency' }, { kind: 'decade-spread' }, { kind: 'star-frequency' }], + seed, + }, + history, + ); + } +}; diff --git a/src/domain/backtest/compareBaselines.test.ts b/src/domain/backtest/compareBaselines.test.ts new file mode 100644 index 0000000..f65fa80 --- /dev/null +++ b/src/domain/backtest/compareBaselines.test.ts @@ -0,0 +1,25 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { compareBaselines } from './compareBaselines.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const sortedAscending = [...history].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + +describe('compareBaselines', () => { + it('reports mean matched numbers/stars for all five baselines', () => { + const range = { start: sortedAscending[600].date, end: sortedAscending[679].date }; + + const comparisons = compareBaselines(history, range, 42); + + expect(comparisons).toHaveLength(5); + for (const comparison of comparisons) { + expect(comparison.meanMatchedNumbers).toBeGreaterThanOrEqual(0); + expect(comparison.meanMatchedNumbers).toBeLessThanOrEqual(5); + expect(comparison.meanMatchedStars).toBeGreaterThanOrEqual(0); + expect(comparison.meanMatchedStars).toBeLessThanOrEqual(2); + } + }); +}); diff --git a/src/domain/backtest/compareBaselines.ts b/src/domain/backtest/compareBaselines.ts new file mode 100644 index 0000000..87c7884 --- /dev/null +++ b/src/domain/backtest/compareBaselines.ts @@ -0,0 +1,20 @@ +import type { Draw } from '../draw/Draw.ts'; +import { BASELINE_KINDS, BASELINE_LABELS, proposeBaselineGrid } from './baselines.ts'; +import type { DateRange } from './runWalkForwardBacktest.ts'; +import { runWalkForwardBacktest } from './runWalkForwardBacktest.ts'; + +export interface BaselineComparison { + baselineName: string; + meanMatchedNumbers: number; + meanMatchedStars: number; +} + +export const compareBaselines = (draws: Draw[], dateRange: DateRange, seed: number): BaselineComparison[] => + BASELINE_KINDS.map((kind) => { + const result = runWalkForwardBacktest((history) => proposeBaselineGrid(kind, history, seed), draws, dateRange); + return { + baselineName: BASELINE_LABELS[kind], + meanMatchedNumbers: result.metrics.meanMatchedNumbers, + meanMatchedStars: result.metrics.meanMatchedStars, + }; + }); diff --git a/src/domain/backtest/detectOverfitting.test.ts b/src/domain/backtest/detectOverfitting.test.ts new file mode 100644 index 0000000..c9fb4b6 --- /dev/null +++ b/src/domain/backtest/detectOverfitting.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import type { BacktestMetrics } from './runWalkForwardBacktest.ts'; +import { detectOverfitting } from './detectOverfitting.ts'; + +const metricsWith = (meanMatchedNumbers: number): BacktestMetrics => ({ + stepCount: 10, + meanMatchedNumbers, + meanMatchedStars: 0.5, + bestPrizeRank: null, + prizeRankCounts: {}, +}); + +describe('detectOverfitting', () => { + it('flags overfitting when train far exceeds validation and test', () => { + const signal = detectOverfitting(metricsWith(3.5), metricsWith(1.0), metricsWith(0.9)); + + expect(signal.isLikelyOverfit).toBe(true); + }); + + it('does not flag overfitting when train/validation/test are comparable', () => { + const signal = detectOverfitting(metricsWith(1.2), metricsWith(1.1), metricsWith(1.0)); + + expect(signal.isLikelyOverfit).toBe(false); + }); + + it('does not flag overfitting when test happens to beat train ("no advantage" is a valid outcome)', () => { + const signal = detectOverfitting(metricsWith(0.8), metricsWith(0.9), metricsWith(1.1)); + + expect(signal.isLikelyOverfit).toBe(false); + }); +}); diff --git a/src/domain/backtest/detectOverfitting.ts b/src/domain/backtest/detectOverfitting.ts new file mode 100644 index 0000000..000a068 --- /dev/null +++ b/src/domain/backtest/detectOverfitting.ts @@ -0,0 +1,38 @@ +import type { BacktestMetrics } from './runWalkForwardBacktest.ts'; + +export interface OverfittingSignal { + isLikelyOverfit: boolean; + trainMeanMatchedNumbers: number; + validationMeanMatchedNumbers: number; + testMeanMatchedNumbers: number; + reason: string; +} + +// Out of a 0-5 matched-numbers scale; a train/validation-or-test gap past +// this is treated as a meaningful divergence rather than ordinary noise. +const OVERFIT_GAP_THRESHOLD = 0.75; + +export const detectOverfitting = ( + trainMetrics: BacktestMetrics, + validationMetrics: BacktestMetrics, + testMetrics: BacktestMetrics, +): OverfittingSignal => { + const trainMean = trainMetrics.meanMatchedNumbers; + const validationMean = validationMetrics.meanMatchedNumbers; + const testMean = testMetrics.meanMatchedNumbers; + + const isLikelyOverfit = + trainMean - validationMean > OVERFIT_GAP_THRESHOLD || trainMean - testMean > OVERFIT_GAP_THRESHOLD; + + const reason = isLikelyOverfit + ? `Train (${trainMean.toFixed(2)} numéros en moyenne) nettement supérieur à validation (${validationMean.toFixed(2)}) et/ou test (${testMean.toFixed(2)}).` + : 'Aucun signe net de surapprentissage : performances train/validation/test comparables.'; + + return { + isLikelyOverfit, + trainMeanMatchedNumbers: trainMean, + validationMeanMatchedNumbers: validationMean, + testMeanMatchedNumbers: testMean, + reason, + }; +}; diff --git a/src/domain/backtest/prizeRank.test.ts b/src/domain/backtest/prizeRank.test.ts new file mode 100644 index 0000000..97b93ad --- /dev/null +++ b/src/domain/backtest/prizeRank.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { countMatches, prizeRank } from './prizeRank.ts'; + +describe('countMatches', () => { + it('counts the intersection size', () => { + expect(countMatches([1, 2, 3, 4, 5], [3, 4, 5, 6, 7])).toBe(3); + expect(countMatches([1, 2], [3, 4])).toBe(0); + expect(countMatches([1, 2], [1, 2])).toBe(2); + }); +}); + +describe('prizeRank', () => { + it('returns rank 1 for a perfect match', () => { + expect(prizeRank(5, 2)).toBe(1); + }); + + it('returns each of the 13 official tiers exactly once', () => { + const ranks = new Set(); + for (let numbers = 0; numbers <= 5; numbers += 1) { + for (let stars = 0; stars <= 2; stars += 1) { + const rank = prizeRank(numbers, stars); + if (rank !== null) ranks.add(rank); + } + } + expect(ranks.size).toBe(13); + expect([...ranks].sort((a, b) => a - b)).toEqual(Array.from({ length: 13 }, (_, i) => i + 1)); + }); + + it('returns null for combinations that win no prize', () => { + expect(prizeRank(0, 0)).toBeNull(); + expect(prizeRank(1, 0)).toBeNull(); + expect(prizeRank(1, 1)).toBeNull(); + expect(prizeRank(0, 1)).toBeNull(); + expect(prizeRank(0, 2)).toBeNull(); + }); +}); diff --git a/src/domain/backtest/prizeRank.ts b/src/domain/backtest/prizeRank.ts new file mode 100644 index 0000000..64eb967 --- /dev/null +++ b/src/domain/backtest/prizeRank.ts @@ -0,0 +1,28 @@ +export const countMatches = (a: readonly number[], b: readonly number[]): number => { + const setB = new Set(b); + return a.filter((n) => setB.has(n)).length; +}; + +/** The 13 official EuroMillions prize tiers, by (matched numbers, matched stars). */ +const RANK_TABLE: { matchedNumbers: number; matchedStars: number; rank: number }[] = [ + { matchedNumbers: 5, matchedStars: 2, rank: 1 }, + { matchedNumbers: 5, matchedStars: 1, rank: 2 }, + { matchedNumbers: 5, matchedStars: 0, rank: 3 }, + { matchedNumbers: 4, matchedStars: 2, rank: 4 }, + { matchedNumbers: 4, matchedStars: 1, rank: 5 }, + { matchedNumbers: 3, matchedStars: 2, rank: 6 }, + { matchedNumbers: 4, matchedStars: 0, rank: 7 }, + { matchedNumbers: 2, matchedStars: 2, rank: 8 }, + { matchedNumbers: 3, matchedStars: 1, rank: 9 }, + { matchedNumbers: 3, matchedStars: 0, rank: 10 }, + { matchedNumbers: 1, matchedStars: 2, rank: 11 }, + { matchedNumbers: 2, matchedStars: 1, rank: 12 }, + { matchedNumbers: 2, matchedStars: 0, rank: 13 }, +]; + +export const prizeRank = (matchedNumbers: number, matchedStars: number): number | null => { + const entry = RANK_TABLE.find( + (candidate) => candidate.matchedNumbers === matchedNumbers && candidate.matchedStars === matchedStars, + ); + return entry ? entry.rank : null; +}; diff --git a/src/domain/backtest/runMonteCarloComparison.test.ts b/src/domain/backtest/runMonteCarloComparison.test.ts new file mode 100644 index 0000000..9fc53c2 --- /dev/null +++ b/src/domain/backtest/runMonteCarloComparison.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { runMonteCarloComparison } from './runMonteCarloComparison.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const sortedAscending = [...history].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); +const range = { start: sortedAscending[600].date, end: sortedAscending[679].date }; + +describe('runMonteCarloComparison', () => { + it('reports a percentile within [0, 100] and a distribution of the requested size', () => { + const result = runMonteCarloComparison(1.5, history, range, 30, 1); + + expect(result.sampleCount).toBe(30); + expect(result.distribution).toHaveLength(30); + expect(result.strategyPercentile).toBeGreaterThanOrEqual(0); + expect(result.strategyPercentile).toBeLessThanOrEqual(100); + }); + + it('is reproducible: the same base seed gives the same distribution', () => { + const a = runMonteCarloComparison(1.5, history, range, 20, 5); + const b = runMonteCarloComparison(1.5, history, range, 20, 5); + + expect(a).toEqual(b); + }); + + it('rates an unrealistically high strategy score near the top percentile', () => { + const result = runMonteCarloComparison(5, history, range, 30, 1); + + expect(result.strategyPercentile).toBeGreaterThan(90); + }); + + it('rates an unrealistically low strategy score near the bottom percentile', () => { + const result = runMonteCarloComparison(0, history, range, 30, 1); + + expect(result.strategyPercentile).toBeLessThan(10); + }); +}); diff --git a/src/domain/backtest/runMonteCarloComparison.ts b/src/domain/backtest/runMonteCarloComparison.ts new file mode 100644 index 0000000..b7a7076 --- /dev/null +++ b/src/domain/backtest/runMonteCarloComparison.ts @@ -0,0 +1,42 @@ +import type { Draw } from '../draw/Draw.ts'; +import { proposeBaselineGrid } from './baselines.ts'; +import type { DateRange } from './runWalkForwardBacktest.ts'; +import { runWalkForwardBacktest } from './runWalkForwardBacktest.ts'; + +export interface MonteCarloResult { + sampleCount: number; + distribution: number[]; + strategyPercentile: number; +} + +/** + * Runs `sampleCount` independent seeded uniform-random walk-forward + * backtests and reports where the tested strategy's mean matched numbers + * falls in that random distribution. "no advantage detected" (percentile + * near 50) is a valid, expected conclusion for most strategies - every + * valid Grid has the same theoretical draw probability. + */ +export const runMonteCarloComparison = ( + strategyMeanMatchedNumbers: number, + draws: Draw[], + dateRange: DateRange, + sampleCount: number, + baseSeed: number, +): MonteCarloResult => { + const distribution: number[] = []; + + for (let i = 0; i < sampleCount; i += 1) { + const seed = baseSeed + i + 1; + const result = runWalkForwardBacktest( + (history) => proposeBaselineGrid('uniform-random', history, seed), + draws, + dateRange, + ); + distribution.push(result.metrics.meanMatchedNumbers); + } + + const belowCount = distribution.filter((value) => value < strategyMeanMatchedNumbers).length; + const strategyPercentile = sampleCount === 0 ? 50 : (belowCount / sampleCount) * 100; + + return { sampleCount, distribution, strategyPercentile }; +}; diff --git a/src/domain/backtest/runWalkForwardBacktest.test.ts b/src/domain/backtest/runWalkForwardBacktest.test.ts new file mode 100644 index 0000000..32675b0 --- /dev/null +++ b/src/domain/backtest/runWalkForwardBacktest.test.ts @@ -0,0 +1,68 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { proposeGrid } from '../strategy/proposeGrid.ts'; +import type { Strategy } from '../strategy/Strategy.ts'; +import { runWalkForwardBacktest } from './runWalkForwardBacktest.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const sortedAscending = [...history].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + +const strategyOf = (rules: Strategy['rules'], seed = 42): Strategy => ({ + id: 'test-strategy', + name: 'Test', + rules, + seed, +}); + +describe('runWalkForwardBacktest', () => { + it('produces one step per draw inside the date range', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }]); + const range = { start: sortedAscending[100].date, end: sortedAscending[109].date }; + + const result = runWalkForwardBacktest((h) => proposeGrid(strategy, h), history, range); + + expect(result.steps).toHaveLength(10); + expect(result.metrics.stepCount).toBe(10); + }); + + it('never lets a proposal depend on draws that happen after it (no future leakage)', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }, { kind: 'recency' }]); + const testRange = { start: sortedAscending[400].date, end: sortedAscending[499].date }; + const truncatedHistory = sortedAscending.slice(0, 500); + + const resultWithFullHistory = runWalkForwardBacktest((h) => proposeGrid(strategy, h), sortedAscending, testRange); + const resultWithTruncatedHistory = runWalkForwardBacktest( + (h) => proposeGrid(strategy, h), + truncatedHistory, + testRange, + ); + + expect(resultWithFullHistory.steps.map((step) => step.proposedGrid)).toEqual( + resultWithTruncatedHistory.steps.map((step) => step.proposedGrid), + ); + }); + + it('is reproducible: the same seed gives the same steps', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }], 123); + const range = { start: sortedAscending[200].date, end: sortedAscending[219].date }; + + const resultA = runWalkForwardBacktest((h) => proposeGrid(strategy, h), history, range); + const resultB = runWalkForwardBacktest((h) => proposeGrid(strategy, h), history, range); + + expect(resultA).toEqual(resultB); + }); + + it('handles an empty date range without throwing, reporting a valid "no signal" result', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }]); + const range = { start: '1900-01-01', end: '1900-01-02' }; + + const result = runWalkForwardBacktest((h) => proposeGrid(strategy, h), history, range); + + expect(result.steps).toHaveLength(0); + expect(result.metrics.meanMatchedNumbers).toBe(0); + expect(result.metrics.bestPrizeRank).toBeNull(); + }); +}); diff --git a/src/domain/backtest/runWalkForwardBacktest.ts b/src/domain/backtest/runWalkForwardBacktest.ts new file mode 100644 index 0000000..684aa95 --- /dev/null +++ b/src/domain/backtest/runWalkForwardBacktest.ts @@ -0,0 +1,92 @@ +import type { Draw } from '../draw/Draw.ts'; +import type { Grid } from '../grid/Grid.ts'; +import { countMatches, prizeRank } from './prizeRank.ts'; + +export type GridProposer = (history: Draw[]) => Grid; + +export interface DateRange { + start: string; + end: string; +} + +export interface BacktestStepResult { + date: string; + actualDraw: Draw; + proposedGrid: Grid; + matchedNumbers: number; + matchedStars: number; + prizeRank: number | null; +} + +export interface BacktestMetrics { + stepCount: number; + meanMatchedNumbers: number; + meanMatchedStars: number; + bestPrizeRank: number | null; + prizeRankCounts: Record; +} + +export interface BacktestResult { + steps: BacktestStepResult[]; + metrics: BacktestMetrics; +} + +const isWithinRange = (date: string, range: DateRange): boolean => date >= range.start && date <= range.end; + +export const computeMetrics = (steps: BacktestStepResult[]): BacktestMetrics => { + const stepCount = steps.length; + const meanMatchedNumbers = + stepCount === 0 ? 0 : steps.reduce((acc, step) => acc + step.matchedNumbers, 0) / stepCount; + const meanMatchedStars = + stepCount === 0 ? 0 : steps.reduce((acc, step) => acc + step.matchedStars, 0) / stepCount; + + const prizeRankCounts: Record = {}; + let bestPrizeRank: number | null = null; + for (const step of steps) { + if (step.prizeRank !== null) { + prizeRankCounts[step.prizeRank] = (prizeRankCounts[step.prizeRank] ?? 0) + 1; + if (bestPrizeRank === null || step.prizeRank < bestPrizeRank) bestPrizeRank = step.prizeRank; + } + } + + return { stepCount, meanMatchedNumbers, meanMatchedStars, bestPrizeRank, prizeRankCounts }; +}; + +/** + * Walk-forward: for each date T in `dateRange`, `propose` only ever sees + * draws strictly before T (sortedAscending.slice(0, index)) - draws at or + * after T, including any beyond `dateRange` itself, can never influence the + * proposal for T. See this file's leakage test for the guarantee. `propose` + * works for both a Strategy (via proposeGrid) and a history-independent + * baseline like fixed-grid, which is why it's a plain function rather than + * a Strategy parameter. + */ +export const runWalkForwardBacktest = ( + propose: GridProposer, + draws: Draw[], + dateRange: DateRange, +): BacktestResult => { + const sortedAscending = [...draws].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + const steps: BacktestStepResult[] = []; + + for (let index = 0; index < sortedAscending.length; index += 1) { + const draw = sortedAscending[index]; + if (!isWithinRange(draw.date, dateRange)) continue; + + const historyBeforeDraw = sortedAscending.slice(0, index); + const proposedGrid = propose(historyBeforeDraw); + const matchedNumbers = countMatches(proposedGrid.numbers, draw.numbers); + const matchedStars = countMatches(proposedGrid.stars, draw.stars); + + steps.push({ + date: draw.date, + actualDraw: draw, + proposedGrid, + matchedNumbers, + matchedStars, + prizeRank: prizeRank(matchedNumbers, matchedStars), + }); + } + + return { steps, metrics: computeMetrics(steps) }; +}; diff --git a/src/domain/backtest/splitDateRanges.test.ts b/src/domain/backtest/splitDateRanges.test.ts new file mode 100644 index 0000000..fdc9fa8 --- /dev/null +++ b/src/domain/backtest/splitDateRanges.test.ts @@ -0,0 +1,42 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { splitDateRanges } from './splitDateRanges.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +const countInRange = (draws: typeof history, range: { start: string; end: string }): number => + draws.filter((draw) => draw.date >= range.start && draw.date <= range.end).length; + +describe('splitDateRanges', () => { + it('produces chronologically ordered, non-overlapping ranges', () => { + const { train, validation, test } = splitDateRanges(history, 0.7, 0.15); + + expect(train.start <= train.end).toBe(true); + expect(train.end < validation.start).toBe(true); + expect(validation.end < test.start).toBe(true); + expect(test.start <= test.end).toBe(true); + }); + + it('covers every draw exactly once across the three ranges', () => { + const { train, validation, test } = splitDateRanges(history, 0.7, 0.15); + + const total = countInRange(history, train) + countInRange(history, validation) + countInRange(history, test); + + expect(total).toBe(history.length); + }); + + it('respects the requested proportions approximately', () => { + const { train, validation } = splitDateRanges(history, 0.7, 0.15); + + const trainCount = countInRange(history, train); + expect(trainCount).toBeGreaterThan(history.length * 0.65); + expect(trainCount).toBeLessThan(history.length * 0.75); + + const validationCount = countInRange(history, validation); + expect(validationCount).toBeGreaterThan(history.length * 0.1); + expect(validationCount).toBeLessThan(history.length * 0.2); + }); +}); diff --git a/src/domain/backtest/splitDateRanges.ts b/src/domain/backtest/splitDateRanges.ts new file mode 100644 index 0000000..253ea82 --- /dev/null +++ b/src/domain/backtest/splitDateRanges.ts @@ -0,0 +1,33 @@ +import type { Draw } from '../draw/Draw.ts'; +import type { DateRange } from './runWalkForwardBacktest.ts'; + +export interface TrainValidationTestRanges { + train: DateRange; + validation: DateRange; + test: DateRange; +} + +/** + * Splits sorted history chronologically - train is the oldest slice, + * validation the middle slice, test the most recent slice. Chronological + * (not random) splitting is what keeps this consistent with the + * walk-forward, no-future-leakage principle: test never precedes train. + */ +export const splitDateRanges = ( + draws: Draw[], + trainRatio: number, + validationRatio: number, +): TrainValidationTestRanges => { + const sortedAscending = [...draws].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + const total = sortedAscending.length; + const trainEndIndex = Math.floor(total * trainRatio); + const validationEndIndex = Math.floor(total * (trainRatio + validationRatio)); + + const dateAt = (index: number): string => sortedAscending[Math.min(Math.max(index, 0), total - 1)].date; + + return { + train: { start: dateAt(0), end: dateAt(trainEndIndex - 1) }, + validation: { start: dateAt(trainEndIndex), end: dateAt(validationEndIndex - 1) }, + test: { start: dateAt(validationEndIndex), end: dateAt(total - 1) }, + }; +}; diff --git a/src/domain/discovery/densityEvaluation.test.ts b/src/domain/discovery/densityEvaluation.test.ts new file mode 100644 index 0000000..bcc1269 --- /dev/null +++ b/src/domain/discovery/densityEvaluation.test.ts @@ -0,0 +1,48 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { extractFeatures } from '../features/FeatureExtractor.ts'; +import { applyNormalization, fitNormalization } from './normalizeFeatures.ts'; +import { reduceDimensionsPca } from './reduceDimensions.ts'; +import { computeDensityEvaluations } from './densityEvaluation.ts'; + +describe('computeDensityEvaluations', () => { + it('rates a point inside a tight cluster as denser than an isolated point', () => { + const rows = [ + [0, 0, 0], + [0.1, 0, 0], + [0, 0.1, 0], + [0.1, 0.1, 0], + [50, 50, 50], // far away, alone + ]; + + const evaluations = computeDensityEvaluations(rows, 2); + + expect(evaluations[0].densityPercentile).toBeGreaterThan(evaluations[4].densityPercentile); + expect(evaluations[4].outlierScore).toBeGreaterThan(evaluations[0].outlierScore); + }); + + it('keeps outlierScore and confidence within [0, 1] and reports k nearest neighbors', () => { + const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); + const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + const vectors = draws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); + const model = fitNormalization(vectors); + const normalizedRows = applyNormalization(vectors, model); + const pcaRows = reduceDimensionsPca(normalizedRows, model.featureKeys).coordinates.map((c) => [c.x, c.y, c.z]); + + const evaluations = computeDensityEvaluations(pcaRows, 10); + + expect(evaluations).toHaveLength(pcaRows.length); + for (const evaluation of evaluations) { + expect(evaluation.outlierScore).toBeGreaterThanOrEqual(0); + expect(evaluation.outlierScore).toBeLessThanOrEqual(1); + expect(evaluation.confidence).toBeGreaterThanOrEqual(0); + expect(evaluation.confidence).toBeLessThanOrEqual(1); + expect(evaluation.densityPercentile).toBeGreaterThanOrEqual(0); + expect(evaluation.densityPercentile).toBeLessThanOrEqual(100); + expect(evaluation.nearestNeighborIndices).toHaveLength(10); + expect(evaluation.nearestNeighborDistances).toHaveLength(10); + } + }); +}); diff --git a/src/domain/discovery/densityEvaluation.ts b/src/domain/discovery/densityEvaluation.ts new file mode 100644 index 0000000..688abd3 --- /dev/null +++ b/src/domain/discovery/densityEvaluation.ts @@ -0,0 +1,60 @@ +const euclidean = (a: number[], b: number[]): number => + Math.sqrt(a.reduce((acc, value, index) => acc + (value - b[index]) ** 2, 0)); + +const mean = (values: number[]): number => values.reduce((acc, value) => acc + value, 0) / values.length; + +const standardDeviation = (values: number[], average: number): number => + Math.sqrt(mean(values.map((value) => (value - average) ** 2))); + +export interface DensityEvaluation { + densityPercentile: number; + outlierScore: number; + confidence: number; + nearestNeighborIndices: number[]; + nearestNeighborDistances: number[]; +} + +/** + * k-NN density: a point in a dense region has small distances to its k + * nearest neighbors; an isolated point has large ones. densityPercentile + * ranks each point against every other point's mean k-NN distance; + * outlierScore is that same distance min-max normalized; confidence is the + * inverse coefficient of variation of the k distances themselves (a tight + * neighbor shell is a more trustworthy density read than a scattered one). + */ +export const computeDensityEvaluations = (rows: number[][], k: number): DensityEvaluation[] => { + const n = rows.length; + + const neighborsOf = rows.map((row, index) => + rows + .map((other, otherIndex) => ({ otherIndex, distance: euclidean(row, other) })) + .filter((entry) => entry.otherIndex !== index) + .sort((a, b) => a.distance - b.distance) + .slice(0, k), + ); + + const meanDistances = neighborsOf.map((neighbors) => mean(neighbors.map((entry) => entry.distance))); + const minDistance = Math.min(...meanDistances); + const maxDistance = Math.max(...meanDistances); + const distanceRange = maxDistance - minDistance || 1; + + return neighborsOf.map((neighbors, index) => { + const meanDistance = meanDistances[index]; + const denserThanCount = meanDistances.filter((distance) => distance > meanDistance).length; + const densityPercentile = n <= 1 ? 100 : (denserThanCount / (n - 1)) * 100; + const outlierScore = (meanDistance - minDistance) / distanceRange; + + const neighborDistances = neighbors.map((entry) => entry.distance); + const stdDev = standardDeviation(neighborDistances, meanDistance); + const coefficientOfVariation = meanDistance === 0 ? 0 : stdDev / meanDistance; + const confidence = Math.max(0, 1 - Math.min(coefficientOfVariation, 1)); + + return { + densityPercentile, + outlierScore, + confidence, + nearestNeighborIndices: neighbors.map((entry) => entry.otherIndex), + nearestNeighborDistances: neighborDistances, + }; + }); +}; diff --git a/src/domain/discovery/generateSyntheticDraws.test.ts b/src/domain/discovery/generateSyntheticDraws.test.ts new file mode 100644 index 0000000..4f5820e --- /dev/null +++ b/src/domain/discovery/generateSyntheticDraws.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { generateSyntheticDraws } from './generateSyntheticDraws.ts'; + +describe('generateSyntheticDraws', () => { + it('produces the requested count of valid, distinct-dated draws', () => { + const draws = generateSyntheticDraws(200, 42, '2020-01-01'); + + expect(draws).toHaveLength(200); + const dates = new Set(draws.map((draw) => draw.date)); + expect(dates.size).toBe(200); + for (const draw of draws) { + expect(new Set(draw.numbers).size).toBe(5); + expect(new Set(draw.stars).size).toBe(2); + expect(draw.numbers.every((n) => n >= 1 && n <= 50)).toBe(true); + expect(draw.stars.every((s) => s >= 1 && s <= 12)).toBe(true); + } + }); + + it('is deterministic for the same seed', () => { + const a = generateSyntheticDraws(50, 7, '2020-01-01'); + const b = generateSyntheticDraws(50, 7, '2020-01-01'); + + expect(a).toEqual(b); + }); + + it('produces different draws for a different seed', () => { + const a = generateSyntheticDraws(50, 1, '2020-01-01'); + const b = generateSyntheticDraws(50, 2, '2020-01-01'); + + expect(a).not.toEqual(b); + }); +}); diff --git a/src/domain/discovery/generateSyntheticDraws.ts b/src/domain/discovery/generateSyntheticDraws.ts new file mode 100644 index 0000000..f8714e3 --- /dev/null +++ b/src/domain/discovery/generateSyntheticDraws.ts @@ -0,0 +1,44 @@ +import type { Draw } from '../draw/Draw.ts'; +import { parseGrid } from '../grid/Grid.ts'; +import { createSeededRandom } from '../random/seededRandom.ts'; + +const pickDistinct = (count: number, min: number, max: number, random: () => number): number[] => { + const pool = Array.from({ length: max - min + 1 }, (_, i) => min + i); + const picked: number[] = []; + + for (let i = 0; i < count; i += 1) { + const index = Math.floor(random() * pool.length); + picked.push(pool[index]); + pool.splice(index, 1); + } + + return picked; +}; + +/** + * Perfectly random, independent draws respecting EuroMillions rules (5 + * distinct numbers 1-50, 2 distinct stars 1-12) - the null hypothesis: + * whatever clustering/density structure this produces is what pure chance + * looks like, the baseline real history's structure gets compared against. + */ +export const generateSyntheticDraws = (count: number, seed: number, startDate: string): Draw[] => { + const random = createSeededRandom(seed); + const start = new Date(startDate); + + return Array.from({ length: count }, (_, index) => { + const grid = parseGrid({ + numbers: pickDistinct(5, 1, 50, random), + stars: pickDistinct(2, 1, 12, random), + }); + const date = new Date(start); + date.setDate(date.getDate() + index * 3); + + return { + id: `synthetic-${index}`, + date: date.toISOString().slice(0, 10), + numbers: grid.numbers, + stars: grid.stars, + source: 'manual', + } satisfies Draw; + }); +}; diff --git a/src/domain/discovery/kmeansClustering.test.ts b/src/domain/discovery/kmeansClustering.test.ts new file mode 100644 index 0000000..8af905f --- /dev/null +++ b/src/domain/discovery/kmeansClustering.test.ts @@ -0,0 +1,83 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { extractFeatures } from '../features/FeatureExtractor.ts'; +import { applyNormalization, fitNormalization } from './normalizeFeatures.ts'; +import { reduceDimensionsPca } from './reduceDimensions.ts'; +import { + computeBootstrapStability, + computeSilhouetteScore, + fitKMeansWithSilhouette, +} from './kmeansClustering.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const vectors = draws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); +const normalizationModel = fitNormalization(vectors); +const normalizedRows = applyNormalization(vectors, normalizationModel); +const pcaCoordinates = reduceDimensionsPca(normalizedRows, normalizationModel.featureKeys).coordinates; +const pcaRows = pcaCoordinates.map((c) => [c.x, c.y, c.z]); + +describe('computeSilhouetteScore', () => { + it('returns 0 for a single cluster (no separation to measure)', () => { + expect(computeSilhouetteScore(pcaRows, pcaRows.map(() => 0))).toBe(0); + }); + + it('returns a score in [-1, 1] for a real clustering', () => { + const { clusters } = { clusters: pcaRows.map((_, i) => i % 3) }; + const score = computeSilhouetteScore(pcaRows, clusters); + expect(score).toBeGreaterThanOrEqual(-1); + expect(score).toBeLessThanOrEqual(1); + }); +}); + +describe('fitKMeansWithSilhouette', () => { + it('picks a k from the candidates and assigns every row to a cluster', () => { + const result = fitKMeansWithSilhouette(pcaRows, [2, 3, 4, 5], 42); + + expect([2, 3, 4, 5]).toContain(result.k); + expect(result.clusterIds).toHaveLength(pcaRows.length); + expect(result.candidateScores).toHaveLength(4); + expect(new Set(result.clusterIds).size).toBe(result.k); + }); + + it('is reproducible for the same seed', () => { + const a = fitKMeansWithSilhouette(pcaRows, [3], 7); + const b = fitKMeansWithSilhouette(pcaRows, [3], 7); + + expect(a).toEqual(b); + }); +}); + +describe('computeBootstrapStability', () => { + it('reports a stability score in [0, 1] for every cluster', () => { + const fit = fitKMeansWithSilhouette(pcaRows, [3], 42); + + const stabilities = computeBootstrapStability(pcaRows, fit.clusterIds, fit.k, 10, 1); + + expect(stabilities).toHaveLength(fit.k); + for (const { stability } of stabilities) { + expect(stability).toBeGreaterThanOrEqual(0); + expect(stability).toBeLessThanOrEqual(1); + } + }); + + it('rates a single well-separated group of identical points as fully stable', () => { + const rows = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [10, 10, 10], + [10, 10, 10], + [10, 10, 10], + ]; + const clusterIds = [0, 0, 0, 1, 1, 1]; + + const stabilities = computeBootstrapStability(rows, clusterIds, 2, 10, 1); + + for (const { stability } of stabilities) { + expect(stability).toBeCloseTo(1, 1); + } + }); +}); diff --git a/src/domain/discovery/kmeansClustering.ts b/src/domain/discovery/kmeansClustering.ts new file mode 100644 index 0000000..3c69a90 --- /dev/null +++ b/src/domain/discovery/kmeansClustering.ts @@ -0,0 +1,150 @@ +import { kmeans } from 'ml-kmeans'; +import { createSeededRandom } from '../random/seededRandom.ts'; + +const squaredEuclidean = (a: number[], b: number[]): number => + a.reduce((acc, value, index) => acc + (value - b[index]) ** 2, 0); + +const euclidean = (a: number[], b: number[]): number => Math.sqrt(squaredEuclidean(a, b)); + +const mean = (values: number[]): number => values.reduce((acc, value) => acc + value, 0) / values.length; + +/** + * Average silhouette coefficient over all points: for each point, how much + * closer it is (on average) to its own cluster than to the nearest other + * cluster. Close to 1 = well-separated clusters, close to 0 or negative = + * the clustering isn't finding real structure. + */ +export const computeSilhouetteScore = (rows: number[][], clusterIds: number[]): number => { + const n = rows.length; + const uniqueClusters = new Set(clusterIds); + if (n < 2 || uniqueClusters.size < 2) return 0; + + let total = 0; + for (let i = 0; i < n; i += 1) { + const own = clusterIds[i]; + const distancesByCluster: Record = {}; + for (let j = 0; j < n; j += 1) { + if (i === j) continue; + (distancesByCluster[clusterIds[j]] ??= []).push(euclidean(rows[i], rows[j])); + } + + const withinClusterDistances = distancesByCluster[own] ?? []; + const a = withinClusterDistances.length > 0 ? mean(withinClusterDistances) : 0; + const otherClusterMeans = Object.entries(distancesByCluster) + .filter(([clusterId]) => Number(clusterId) !== own) + .map(([, distances]) => mean(distances)); + const b = otherClusterMeans.length > 0 ? Math.min(...otherClusterMeans) : 0; + + total += Math.max(a, b) === 0 ? 0 : (b - a) / Math.max(a, b); + } + + return total / n; +}; + +export interface KMeansCandidateScore { + k: number; + silhouetteScore: number; +} + +export interface KMeansFitResult { + k: number; + clusterIds: number[]; + centroids: number[][]; + silhouetteScore: number; + candidateScores: KMeansCandidateScore[]; +} + +/** Tries every k in `kCandidates`, keeps the one with the best silhouette score. */ +export const fitKMeansWithSilhouette = (rows: number[][], kCandidates: number[], seed: number): KMeansFitResult => { + const candidates = kCandidates.map((k) => { + const result = kmeans(rows, k, { seed, initialization: 'kmeans++' }); + return { k, silhouetteScore: computeSilhouetteScore(rows, result.clusters), result }; + }); + + const best = candidates.reduce((a, b) => (b.silhouetteScore > a.silhouetteScore ? b : a)); + + return { + k: best.k, + clusterIds: best.result.clusters, + centroids: best.result.centroids, + silhouetteScore: best.silhouetteScore, + candidateScores: candidates.map(({ k, silhouetteScore }) => ({ k, silhouetteScore })), + }; +}; + +export interface ClusterStability { + clusterId: number; + stability: number; +} + +const MAX_PAIRS_PER_CLUSTER = 200; + +const samplePairs = (indices: number[], random: () => number): [number, number][] => { + const maxPairs = Math.min(MAX_PAIRS_PER_CLUSTER, Math.floor((indices.length * (indices.length - 1)) / 2)); + const pairs: [number, number][] = []; + const seen = new Set(); + let attempts = 0; + + while (pairs.length < maxPairs && attempts < maxPairs * 20) { + attempts += 1; + const a = indices[Math.floor(random() * indices.length)]; + const b = indices[Math.floor(random() * indices.length)]; + if (a === b) continue; + const key = a < b ? `${a}-${b}` : `${b}-${a}`; + if (seen.has(key)) continue; + seen.add(key); + pairs.push(a < b ? [a, b] : [b, a]); + } + + return pairs; +}; + +/** + * Bootstrap stability: resample rows with replacement, refit K-Means on the + * resample, then check whether pairs of points originally co-clustered + * stay co-clustered under the resampled model. Averaged over + * `bootstrapIterations`. A simplified co-clustering agreement rate rather + * than a full Adjusted Rand Index - cheaper, and enough to flag "this + * family doesn't hold up" per the spec's stability requirement. + */ +export const computeBootstrapStability = ( + rows: number[][], + clusterIds: number[], + k: number, + bootstrapIterations: number, + seed: number, +): ClusterStability[] => { + const random = createSeededRandom(seed); + const n = rows.length; + + const indicesByCluster: Record = {}; + clusterIds.forEach((clusterId, index) => { + (indicesByCluster[clusterId] ??= []).push(index); + }); + + const pairsByCluster: Record = {}; + for (const [clusterId, indices] of Object.entries(indicesByCluster)) { + pairsByCluster[Number(clusterId)] = samplePairs(indices, random); + } + + const agreementCounts: Record = {}; + for (const clusterId of Object.keys(indicesByCluster)) agreementCounts[Number(clusterId)] = 0; + + for (let iteration = 0; iteration < bootstrapIterations; iteration += 1) { + const resampledRows = Array.from({ length: n }, () => rows[Math.floor(random() * n)]); + const bootstrapModel = kmeans(resampledRows, k, { seed: seed + iteration + 1, initialization: 'kmeans++' }); + const assignments = bootstrapModel.nearest(rows); + + for (const [clusterId, pairs] of Object.entries(pairsByCluster)) { + for (const [a, b] of pairs) { + if (assignments[a] === assignments[b]) agreementCounts[Number(clusterId)] += 1; + } + } + } + + return Object.entries(indicesByCluster).map(([clusterId]) => { + const id = Number(clusterId); + const totalChecks = pairsByCluster[id].length * bootstrapIterations; + return { clusterId: id, stability: totalChecks === 0 ? 1 : agreementCounts[id] / totalChecks }; + }); +}; diff --git a/src/domain/discovery/normalizeFeatures.test.ts b/src/domain/discovery/normalizeFeatures.test.ts new file mode 100644 index 0000000..33aa272 --- /dev/null +++ b/src/domain/discovery/normalizeFeatures.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { extractFeatures, FEATURE_KEYS } from '../features/FeatureExtractor.ts'; +import { applyNormalization, fitNormalization } from './normalizeFeatures.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const realVectors = draws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); + +const vectorOf = (overrides: Record) => ({ + values: Object.fromEntries(FEATURE_KEYS.map((key) => [key, overrides[key] ?? 0])), +}); + +describe('fitNormalization', () => { + it('excludes a feature that never varies (near-constant)', () => { + const vectors = [ + vectorOf({ sum: 10 }), + vectorOf({ sum: 20 }), + vectorOf({ sum: 30 }), + // clusterSizeMax constant across all rows here (default 0) + ]; + + const model = fitNormalization(vectors); + + expect(model.featureKeys).not.toContain('clusterSizeMax'); + expect(model.excludedFeatures).toContainEqual({ key: 'clusterSizeMax', reason: 'near-constant' }); + }); + + it('excludes a feature that is perfectly redundant with an earlier one', () => { + const vectors = [1, 2, 3, 4, 5].map((n) => vectorOf({ sum: n, range: n * 2 })); + + const model = fitNormalization(vectors); + + expect(model.featureKeys).toContain('sum'); + expect(model.featureKeys).not.toContain('range'); + expect(model.excludedFeatures).toContainEqual({ key: 'range', reason: 'redundant', correlatedWith: 'sum' }); + }); + + it('produces a model whose stats reflect the real historical dataset', () => { + const model = fitNormalization(realVectors); + + expect(model.featureKeys.length).toBeGreaterThan(0); + expect(model.featureKeys.length).toBeLessThanOrEqual(FEATURE_KEYS.length); + for (const key of model.featureKeys) { + expect(Number.isFinite(model.means[key])).toBe(true); + expect(model.stdDevs[key]).toBeGreaterThan(0); + } + }); +}); + +describe('applyNormalization', () => { + it('transforms a new, unseen point using the fitted (frozen) stats, not the new point\'s own', () => { + const model = fitNormalization(realVectors); + const unseenPoint = vectorOf({ sum: 999 }); // wildly different from training data + + const [normalized] = applyNormalization([unseenPoint], model); + const sumIndex = model.featureKeys.indexOf('sum'); + + // Standardized against the training mean/stddev, so a far-off raw value + // produces a large z-score - not a value near 0, which is what it would + // be if normalization were (wrongly) refit on this single new point. + expect(Math.abs(normalized[sumIndex])).toBeGreaterThan(3); + }); + + it('produces one row per input vector, with one column per surviving feature', () => { + const model = fitNormalization(realVectors); + + const normalized = applyNormalization(realVectors.slice(0, 5), model); + + expect(normalized).toHaveLength(5); + for (const row of normalized) { + expect(row).toHaveLength(model.featureKeys.length); + expect(row.every((value) => Number.isFinite(value))).toBe(true); + } + }); +}); diff --git a/src/domain/discovery/normalizeFeatures.ts b/src/domain/discovery/normalizeFeatures.ts new file mode 100644 index 0000000..d84d835 --- /dev/null +++ b/src/domain/discovery/normalizeFeatures.ts @@ -0,0 +1,93 @@ +import type { FeatureVector } from '../features/FeatureExtractor.ts'; +import { FEATURE_KEYS } from '../features/FeatureExtractor.ts'; + +export interface ExcludedFeature { + key: string; + reason: 'near-constant' | 'redundant'; + correlatedWith?: string; +} + +export interface NormalizationModel { + featureKeys: string[]; + excludedFeatures: ExcludedFeature[]; + means: Record; + stdDevs: Record; +} + +const NEAR_CONSTANT_STD_THRESHOLD = 1e-9; +const REDUNDANCY_CORRELATION_THRESHOLD = 0.95; + +const mean = (values: number[]): number => values.reduce((acc, value) => acc + value, 0) / values.length; + +const standardDeviation = (values: number[], average: number): number => + Math.sqrt(mean(values.map((value) => (value - average) ** 2))); + +const pearsonCorrelation = (a: number[], b: number[]): number => { + const meanA = mean(a); + const meanB = mean(b); + let numerator = 0; + let denomA = 0; + let denomB = 0; + + for (let i = 0; i < a.length; i += 1) { + const da = a[i] - meanA; + const db = b[i] - meanB; + numerator += da * db; + denomA += da * da; + denomB += db * db; + } + + const denom = Math.sqrt(denomA * denomB); + return denom === 0 ? 0 : numerator / denom; +}; + +/** + * Fit once on the reference dataset (means/stdDevs frozen here); never + * refit when transforming new points later - that's what keeps a future + * "evaluate one new grid against the discovered structure" consumer from + * leaking that point's own value into its own normalization. + */ +export const fitNormalization = (trainVectors: FeatureVector[]): NormalizationModel => { + const excludedFeatures: ExcludedFeature[] = []; + const means: Record = {}; + const stdDevs: Record = {}; + const columnsByKey: Record = {}; + + for (const key of FEATURE_KEYS) { + const column = trainVectors.map((vector) => vector.values[key]); + const average = mean(column); + means[key] = average; + stdDevs[key] = standardDeviation(column, average); + columnsByKey[key] = column; + } + + const afterConstantCheck = FEATURE_KEYS.filter((key) => { + if (stdDevs[key] < NEAR_CONSTANT_STD_THRESHOLD) { + excludedFeatures.push({ key, reason: 'near-constant' }); + return false; + } + return true; + }); + + const survivors: string[] = []; + for (const key of afterConstantCheck) { + const redundantWith = survivors.find( + (kept) => Math.abs(pearsonCorrelation(columnsByKey[key], columnsByKey[kept])) > REDUNDANCY_CORRELATION_THRESHOLD, + ); + if (redundantWith) { + excludedFeatures.push({ key, reason: 'redundant', correlatedWith: redundantWith }); + } else { + survivors.push(key); + } + } + + return { featureKeys: survivors, excludedFeatures, means, stdDevs }; +}; + +export const applyNormalization = (vectors: FeatureVector[], model: NormalizationModel): number[][] => + vectors.map((vector) => + model.featureKeys.map((key) => { + const sd = model.stdDevs[key]; + return sd === 0 ? 0 : (vector.values[key] - model.means[key]) / sd; + }), + ); diff --git a/src/domain/discovery/reduceDimensions.test.ts b/src/domain/discovery/reduceDimensions.test.ts new file mode 100644 index 0000000..8d9e736 --- /dev/null +++ b/src/domain/discovery/reduceDimensions.test.ts @@ -0,0 +1,45 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { extractFeatures } from '../features/FeatureExtractor.ts'; +import { applyNormalization, fitNormalization } from './normalizeFeatures.ts'; +import { reduceDimensionsPca } from './reduceDimensions.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const draws = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); +const vectors = draws.map((draw) => extractFeatures({ numbers: draw.numbers, stars: draw.stars })); +const normalizationModel = fitNormalization(vectors); +const normalizedRows = applyNormalization(vectors, normalizationModel); + +describe('reduceDimensionsPca', () => { + it('produces one 3D coordinate per input row', () => { + const { coordinates } = reduceDimensionsPca(normalizedRows, normalizationModel.featureKeys); + + expect(coordinates).toHaveLength(vectors.length); + for (const coordinate of coordinates) { + expect(Number.isFinite(coordinate.x)).toBe(true); + expect(Number.isFinite(coordinate.y)).toBe(true); + expect(Number.isFinite(coordinate.z)).toBe(true); + } + }); + + it('reports explained variance as decreasing proportions summing to <= 1', () => { + const { explainedVariance } = reduceDimensionsPca(normalizedRows, normalizationModel.featureKeys); + + expect(explainedVariance).toHaveLength(3); + expect(explainedVariance[0]).toBeGreaterThanOrEqual(explainedVariance[1]); + expect(explainedVariance[1]).toBeGreaterThanOrEqual(explainedVariance[2]); + expect(explainedVariance.reduce((acc, v) => acc + v, 0)).toBeLessThanOrEqual(1.0001); + }); + + it('reports one contribution (loading) entry per surviving feature', () => { + const { featureContributions } = reduceDimensionsPca(normalizedRows, normalizationModel.featureKeys); + + expect(featureContributions).toHaveLength(normalizationModel.featureKeys.length); + for (const contribution of featureContributions) { + expect(normalizationModel.featureKeys).toContain(contribution.feature); + expect(Number.isFinite(contribution.pc1)).toBe(true); + } + }); +}); diff --git a/src/domain/discovery/reduceDimensions.ts b/src/domain/discovery/reduceDimensions.ts new file mode 100644 index 0000000..8f90a5e --- /dev/null +++ b/src/domain/discovery/reduceDimensions.ts @@ -0,0 +1,45 @@ +import { PCA } from 'ml-pca'; + +export interface Coordinates3D { + x: number; + y: number; + z: number; +} + +export interface FeatureContribution { + feature: string; + pc1: number; + pc2: number; + pc3: number; +} + +export interface DimensionalityReduction { + coordinates: Coordinates3D[]; + explainedVariance: number[]; + featureContributions: FeatureContribution[]; +} + +/** + * Expects already-normalized rows (see normalizeFeatures.ts) - scale: false + * because those rows are already zero-mean/unit-variance; PCA only centers + * (a no-op here, kept for robustness) rather than re-scaling. + */ +export const reduceDimensionsPca = (normalizedRows: number[][], featureKeys: string[]): DimensionalityReduction => { + const pca = new PCA(normalizedRows, { center: true, scale: false }); + const projected = pca.predict(normalizedRows, { nComponents: 3 }).to2DArray(); + const coordinates = projected.map(([x, y, z]) => ({ x, y: y ?? 0, z: z ?? 0 })); + + // getExplainedVariance() already returns proportions of total variance (sum to 1). + const explainedVariance = pca.getExplainedVariance().slice(0, 3); + + const loadings = pca.getLoadings().to2DArray(); + const [pc1Loadings, pc2Loadings, pc3Loadings] = loadings; + const featureContributions = featureKeys.map((feature, index) => ({ + feature, + pc1: pc1Loadings?.[index] ?? 0, + pc2: pc2Loadings?.[index] ?? 0, + pc3: pc3Loadings?.[index] ?? 0, + })); + + return { coordinates, explainedVariance, featureContributions }; +}; diff --git a/src/domain/draw/Draw.ts b/src/domain/draw/Draw.ts new file mode 100644 index 0000000..dd0a115 --- /dev/null +++ b/src/domain/draw/Draw.ts @@ -0,0 +1,10 @@ +import type { Grid } from '../grid/Grid.ts'; + +export interface Draw { + id: string; + date: string; + numbers: Grid['numbers']; + stars: Grid['stars']; + jackpot?: number; + source: 'fdj-csv' | 'api' | 'manual'; +} diff --git a/src/domain/draw/findExactMatch.test.ts b/src/domain/draw/findExactMatch.test.ts new file mode 100644 index 0000000..b04dda2 --- /dev/null +++ b/src/domain/draw/findExactMatch.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from '../grid/Grid.ts'; +import type { Draw } from './Draw.ts'; +import { findExactMatch, findNumbersOnlyMatches } from './findExactMatch.ts'; + +const buildDraw = (overrides: Partial = {}): Draw => ({ + id: '1', + date: '2024-01-05', + numbers: [3, 7, 19, 31, 42], + stars: [2, 9], + source: 'fdj-csv', + ...overrides, +}); + +describe('findExactMatch', () => { + it('finds the draw matching the same numbers and stars', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const draw = buildDraw(); + + expect(findExactMatch(grid, [draw])).toEqual(draw); + }); + + it('is invariant to input order, since Grid is always sorted', () => { + const grid = parseGrid({ numbers: [42, 31, 19, 7, 3], stars: [9, 2] }); + const draw = buildDraw(); + + expect(findExactMatch(grid, [draw])).toEqual(draw); + }); + + it('returns null when no draw matches', () => { + const grid = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + const draw = buildDraw(); + + expect(findExactMatch(grid, [draw])).toBeNull(); + }); + + it('does not match when only the numbers are the same and the stars differ', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [1, 5] }); + const draw = buildDraw(); + + expect(findExactMatch(grid, [draw])).toBeNull(); + }); + + it('returns null for an empty history', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(findExactMatch(grid, [])).toBeNull(); + }); +}); + +describe('findNumbersOnlyMatches', () => { + it('matches a draw with the same numbers but different stars', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [1, 5] }); + const draw = buildDraw(); + + expect(findNumbersOnlyMatches(grid, [draw])).toEqual([draw]); + }); + + it('also matches a draw with the same numbers and the same stars', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const draw = buildDraw(); + + expect(findNumbersOnlyMatches(grid, [draw])).toEqual([draw]); + }); + + it('returns every matching draw, not just the first', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [1, 5] }); + const first = buildDraw({ id: '1', date: '2021-03-01', stars: [4, 8] }); + const second = buildDraw({ id: '2', date: '2023-06-10', stars: [1, 5] }); + + expect(findNumbersOnlyMatches(grid, [first, second])).toEqual([first, second]); + }); + + it('returns an empty array when no draw shares the same numbers', () => { + const grid = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + const draw = buildDraw(); + + expect(findNumbersOnlyMatches(grid, [draw])).toEqual([]); + }); +}); diff --git a/src/domain/draw/findExactMatch.ts b/src/domain/draw/findExactMatch.ts new file mode 100644 index 0000000..a4e5b48 --- /dev/null +++ b/src/domain/draw/findExactMatch.ts @@ -0,0 +1,13 @@ +import type { Grid } from '../grid/Grid.ts'; +import type { Draw } from './Draw.ts'; + +export const findExactMatch = (grid: Grid, draws: Draw[]): Draw | null => + draws.find( + (draw) => + draw.numbers.every((number, index) => number === grid.numbers[index]) && + draw.stars.every((star, index) => star === grid.stars[index]), + ) ?? null; + +/** Draws sharing the same 5 numbers, regardless of which stars came out - a looser match than findExactMatch. */ +export const findNumbersOnlyMatches = (grid: Grid, draws: Draw[]): Draw[] => + draws.filter((draw) => draw.numbers.every((number, index) => number === grid.numbers[index])); diff --git a/src/domain/features/FeatureExtractor.test.ts b/src/domain/features/FeatureExtractor.test.ts new file mode 100644 index 0000000..a45ed9a --- /dev/null +++ b/src/domain/features/FeatureExtractor.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from '../grid/Grid.ts'; +import { FEATURE_KEYS, extractFeatures, featureVectorToArray } from './FeatureExtractor.ts'; + +describe('extractFeatures', () => { + it('is deterministic for the same grid', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(extractFeatures(grid)).toEqual(extractFeatures(grid)); + }); + + it('is invariant to the order numbers/stars were originally given in', () => { + const a = parseGrid({ numbers: [42, 3, 19, 7, 31], stars: [9, 2] }); + const b = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(extractFeatures(a)).toEqual(extractFeatures(b)); + }); + + it('computes sum, range and parity counts consistently', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const { values } = extractFeatures(grid); + + expect(values.sum).toBe(3 + 7 + 19 + 31 + 42); + expect(values.min).toBe(3); + expect(values.max).toBe(42); + expect(values.range).toBe(39); + expect(values.oddCount + values.evenCount).toBe(5); + expect(values.decade1 + values.decade2 + values.decade3 + values.decade4 + values.decade5).toBe(5); + }); + + it('computes the four successive gaps from the sorted numbers', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const { values } = extractFeatures(grid); + + expect([values.gap1, values.gap2, values.gap3, values.gap4]).toEqual([4, 12, 12, 11]); + }); + + it('flags a number above 31 and a pair of consecutive numbers', () => { + const grid = parseGrid({ numbers: [3, 4, 19, 31, 42], stars: [2, 9] }); + const { values } = extractFeatures(grid); + + expect(values.consecutivePairsCount).toBe(1); + expect(values.aboveThirtyOneCount).toBe(1); + }); + + it('exposes one array entry per declared feature key, in a stable order', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const vector = extractFeatures(grid); + + const array = featureVectorToArray(vector); + + expect(array).toHaveLength(FEATURE_KEYS.length); + expect(array.every((value) => Number.isFinite(value))).toBe(true); + }); +}); diff --git a/src/domain/features/FeatureExtractor.ts b/src/domain/features/FeatureExtractor.ts new file mode 100644 index 0000000..0117bb1 --- /dev/null +++ b/src/domain/features/FeatureExtractor.ts @@ -0,0 +1,140 @@ +import type { Grid } from '../grid/Grid.ts'; + +export interface FeatureVector { + values: Record; +} + +export const FEATURE_KEYS = [ + 'sum', + 'mean', + 'median', + 'min', + 'max', + 'range', + 'variance', + 'stdDev', + 'decade1', + 'decade2', + 'decade3', + 'decade4', + 'decade5', + 'occupiedBucketCount', + 'clusterSizeMax', + 'entropy', + 'oddCount', + 'evenCount', + 'parityAlternations', + 'gap1', + 'gap2', + 'gap3', + 'gap4', + 'gapMean', + 'gapStdDev', + 'consecutivePairsCount', + 'sameUnitsPairsCount', + 'multiplesOfFiveCount', + 'aboveThirtyOneCount', + 'starsSum', + 'starsDiff', + 'starsOddCount', +] as const; + +export type FeatureKey = (typeof FEATURE_KEYS)[number]; + +export const extractFeatures = (grid: Grid): FeatureVector => { + const { numbers, stars } = grid; + + const sum = numbers.reduce((acc, n) => acc + n, 0); + const mean = sum / numbers.length; + const median = numbers[2]; + const min = numbers[0]; + const max = numbers[4]; + const range = max - min; + const variance = numbers.reduce((acc, n) => acc + (n - mean) ** 2, 0) / numbers.length; + const stdDev = Math.sqrt(variance); + + const decadeBuckets = [0, 0, 0, 0, 0]; + for (const n of numbers) { + decadeBuckets[Math.min(Math.floor((n - 1) / 10), 4)] += 1; + } + const occupiedBucketCount = decadeBuckets.filter((count) => count > 0).length; + const clusterSizeMax = Math.max(...decadeBuckets); + const entropy = decadeBuckets + .filter((count) => count > 0) + .reduce((acc, count) => { + const p = count / numbers.length; + return acc - p * Math.log2(p); + }, 0); + + const oddCount = numbers.filter((n) => n % 2 !== 0).length; + const evenCount = numbers.length - oddCount; + let parityAlternations = 0; + for (let i = 0; i < numbers.length - 1; i += 1) { + if (numbers[i] % 2 !== numbers[i + 1] % 2) parityAlternations += 1; + } + + const gaps: [number, number, number, number] = [ + numbers[1] - numbers[0], + numbers[2] - numbers[1], + numbers[3] - numbers[2], + numbers[4] - numbers[3], + ]; + const gapMean = gaps.reduce((acc, g) => acc + g, 0) / gaps.length; + const gapVariance = gaps.reduce((acc, g) => acc + (g - gapMean) ** 2, 0) / gaps.length; + const gapStdDev = Math.sqrt(gapVariance); + const consecutivePairsCount = gaps.filter((gap) => gap === 1).length; + + let sameUnitsPairsCount = 0; + for (let i = 0; i < numbers.length; i += 1) { + for (let j = i + 1; j < numbers.length; j += 1) { + if (numbers[i] % 10 === numbers[j] % 10) sameUnitsPairsCount += 1; + } + } + + const multiplesOfFiveCount = numbers.filter((n) => n % 5 === 0).length; + const aboveThirtyOneCount = numbers.filter((n) => n > 31).length; + + const starsSum = stars[0] + stars[1]; + const starsDiff = Math.abs(stars[1] - stars[0]); + const starsOddCount = stars.filter((s) => s % 2 !== 0).length; + + const values: Record = { + sum, + mean, + median, + min, + max, + range, + variance, + stdDev, + decade1: decadeBuckets[0], + decade2: decadeBuckets[1], + decade3: decadeBuckets[2], + decade4: decadeBuckets[3], + decade5: decadeBuckets[4], + occupiedBucketCount, + clusterSizeMax, + entropy, + oddCount, + evenCount, + parityAlternations, + gap1: gaps[0], + gap2: gaps[1], + gap3: gaps[2], + gap4: gaps[3], + gapMean, + gapStdDev, + consecutivePairsCount, + sameUnitsPairsCount, + multiplesOfFiveCount, + aboveThirtyOneCount, + starsSum, + starsDiff, + starsOddCount, + }; + + return { values }; +}; + +export const featureVectorToArray = (vector: FeatureVector): number[] => + FEATURE_KEYS.map((key) => vector.values[key]); diff --git a/src/domain/geometry/GeometryDescriptor.test.ts b/src/domain/geometry/GeometryDescriptor.test.ts new file mode 100644 index 0000000..2baa74b --- /dev/null +++ b/src/domain/geometry/GeometryDescriptor.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from '../grid/Grid.ts'; +import { buildGeometryDescriptor } from './GeometryDescriptor.ts'; + +describe('buildGeometryDescriptor', () => { + it('mirrors the underlying FeatureVector values in the V1-spec shape', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + const descriptor = buildGeometryDescriptor(grid); + const { values } = descriptor.features; + + expect(descriptor.grid).toEqual(grid); + expect(descriptor.sum).toBe(values.sum); + expect(descriptor.range).toBe(values.range); + expect(descriptor.oddCount).toBe(values.oddCount); + expect(descriptor.evenCount).toBe(values.evenCount); + expect(descriptor.clusterSizeMax).toBe(values.clusterSizeMax); + expect(descriptor.decadeBuckets).toEqual([ + values.decade1, + values.decade2, + values.decade3, + values.decade4, + values.decade5, + ]); + expect(descriptor.gaps).toEqual([values.gap1, values.gap2, values.gap3, values.gap4]); + }); + + it('is a pure, deterministic function of the grid', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(buildGeometryDescriptor(grid)).toEqual(buildGeometryDescriptor(grid)); + }); +}); diff --git a/src/domain/geometry/GeometryDescriptor.ts b/src/domain/geometry/GeometryDescriptor.ts new file mode 100644 index 0000000..b6de59c --- /dev/null +++ b/src/domain/geometry/GeometryDescriptor.ts @@ -0,0 +1,32 @@ +import type { Grid } from '../grid/Grid.ts'; +import type { FeatureVector } from '../features/FeatureExtractor.ts'; +import { extractFeatures } from '../features/FeatureExtractor.ts'; + +export interface GeometryDescriptor { + grid: Grid; + features: FeatureVector; + decadeBuckets: [number, number, number, number, number]; + gaps: [number, number, number, number]; + sum: number; + range: number; + oddCount: number; + evenCount: number; + clusterSizeMax: number; +} + +export const buildGeometryDescriptor = (grid: Grid): GeometryDescriptor => { + const features = extractFeatures(grid); + const v = features.values; + + return { + grid, + features, + decadeBuckets: [v.decade1, v.decade2, v.decade3, v.decade4, v.decade5], + gaps: [v.gap1, v.gap2, v.gap3, v.gap4], + sum: v.sum, + range: v.range, + oddCount: v.oddCount, + evenCount: v.evenCount, + clusterSizeMax: v.clusterSizeMax, + }; +}; diff --git a/src/domain/geometry/GeometryDistance.test.ts b/src/domain/geometry/GeometryDistance.test.ts new file mode 100644 index 0000000..ec190ab --- /dev/null +++ b/src/domain/geometry/GeometryDistance.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from '../grid/Grid.ts'; +import { buildGeometryDescriptor } from './GeometryDescriptor.ts'; +import { computeGeometryDistance } from './GeometryDistance.ts'; + +const descriptorOf = (numbers: number[], stars: number[]) => + buildGeometryDescriptor(parseGrid({ numbers, stars })); + +describe('computeGeometryDistance', () => { + it('is zero for a grid compared to itself', () => { + const descriptor = descriptorOf([3, 7, 19, 31, 42], [2, 9]); + + const distance = computeGeometryDistance(descriptor, descriptor); + + expect(distance.total).toBe(0); + for (const value of Object.values(distance.components)) { + expect(value).toBe(0); + } + }); + + it('is symmetric', () => { + const a = descriptorOf([3, 7, 19, 31, 42], [2, 9]); + const b = descriptorOf([1, 2, 3, 4, 5], [1, 2]); + + expect(computeGeometryDistance(a, b)).toEqual(computeGeometryDistance(b, a)); + }); + + it('always exposes its components alongside the total', () => { + const a = descriptorOf([3, 7, 19, 31, 42], [2, 9]); + const b = descriptorOf([1, 2, 3, 4, 5], [1, 2]); + + const distance = computeGeometryDistance(a, b); + + expect(Object.keys(distance.components).sort()).toEqual( + ['buckets', 'gaps', 'numbers', 'parity', 'stars', 'sum'].sort(), + ); + }); + + it('rates a near-identical grid as closer than a wildly different one', () => { + const reference = descriptorOf([3, 7, 19, 31, 42], [2, 9]); + const almostSame = descriptorOf([3, 7, 19, 31, 43], [2, 9]); + const veryDifferent = descriptorOf([1, 2, 3, 4, 5], [1, 2]); + + const closeDistance = computeGeometryDistance(reference, almostSame); + const farDistance = computeGeometryDistance(reference, veryDifferent); + + expect(closeDistance.total).toBeLessThan(farDistance.total); + }); +}); diff --git a/src/domain/geometry/GeometryDistance.ts b/src/domain/geometry/GeometryDistance.ts new file mode 100644 index 0000000..414340c --- /dev/null +++ b/src/domain/geometry/GeometryDistance.ts @@ -0,0 +1,49 @@ +import type { GeometryDescriptor } from './GeometryDescriptor.ts'; + +export interface GeometryDistance { + total: number; + components: Record; +} + +const jaccardDistance = (a: readonly number[], b: readonly number[]): number => { + const setA = new Set(a); + const setB = new Set(b); + const union = new Set([...setA, ...setB]); + const intersectionSize = [...setA].filter((value) => setB.has(value)).length; + return 1 - intersectionSize / union.size; +}; + +const MAX_SUM_RANGE = 240 - 15; // (46+47+48+49+50) - (1+2+3+4+5) +const MAX_GAP = 49; // widest possible single gap (numbers 1 and 50) + +export const computeGeometryDistance = ( + a: GeometryDescriptor, + b: GeometryDescriptor, +): GeometryDistance => { + const bucketsDistance = + a.decadeBuckets.reduce((acc, count, index) => acc + Math.abs(count - b.decadeBuckets[index]), 0) / + 10; + + const sumDistance = Math.abs(a.sum - b.sum) / MAX_SUM_RANGE; + + const gapsDistance = + a.gaps.reduce((acc, gap, index) => acc + Math.abs(gap - b.gaps[index]), 0) / + a.gaps.length / + MAX_GAP; + + const parityDistance = Math.abs(a.oddCount - b.oddCount) / a.grid.numbers.length; + + const components: Record = { + numbers: jaccardDistance(a.grid.numbers, b.grid.numbers), + buckets: bucketsDistance, + sum: sumDistance, + gaps: gapsDistance, + parity: parityDistance, + stars: jaccardDistance(a.grid.stars, b.grid.stars), + }; + + const total = + Object.values(components).reduce((acc, value) => acc + value, 0) / Object.keys(components).length; + + return { total, components }; +}; diff --git a/src/domain/geometry/SpatialEmbedding.ts b/src/domain/geometry/SpatialEmbedding.ts new file mode 100644 index 0000000..839d56e --- /dev/null +++ b/src/domain/geometry/SpatialEmbedding.ts @@ -0,0 +1,18 @@ +export type EmbeddingMethod = 'pca' | 'umap'; + +export interface SpatialEmbedding { + drawId: string; + coordinates: { + x: number; + y: number; + z: number; + }; + method: EmbeddingMethod; + clusterId?: string; + density: number; + outlierScore: number; + nearestNeighbors: { + drawId: string; + distance: number; + }[]; +} diff --git a/src/domain/geometry/nearestNeighbors.test.ts b/src/domain/geometry/nearestNeighbors.test.ts new file mode 100644 index 0000000..f6397ab --- /dev/null +++ b/src/domain/geometry/nearestNeighbors.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from '../grid/Grid.ts'; +import { buildGeometryDescriptor } from './GeometryDescriptor.ts'; +import { findNearestNeighbors } from './nearestNeighbors.ts'; + +const descriptorOf = (numbers: number[], stars: number[]) => + buildGeometryDescriptor(parseGrid({ numbers, stars })); + +describe('findNearestNeighbors', () => { + it('returns the k closest candidates ordered by ascending distance', () => { + const target = descriptorOf([3, 7, 19, 31, 42], [2, 9]); + const candidates = [ + { item: 'almost-same', descriptor: descriptorOf([3, 7, 19, 31, 43], [2, 9]) }, + { item: 'very-different', descriptor: descriptorOf([1, 2, 3, 4, 5], [1, 2]) }, + { item: 'somewhat-different', descriptor: descriptorOf([3, 7, 19, 30, 42], [2, 9]) }, + ]; + + const neighbors = findNearestNeighbors(target, candidates, 2); + + expect(neighbors).toHaveLength(2); + expect(neighbors[0].item).toBe('almost-same'); + expect(neighbors[0].distance).toBeLessThanOrEqual(neighbors[1].distance); + }); +}); diff --git a/src/domain/geometry/nearestNeighbors.ts b/src/domain/geometry/nearestNeighbors.ts new file mode 100644 index 0000000..2ce98f6 --- /dev/null +++ b/src/domain/geometry/nearestNeighbors.ts @@ -0,0 +1,27 @@ +import type { GeometryDescriptor } from './GeometryDescriptor.ts'; +import { computeGeometryDistance } from './GeometryDistance.ts'; + +export interface GeometryCandidate { + item: T; + descriptor: GeometryDescriptor; +} + +export interface NearestNeighbor { + item: T; + descriptor: GeometryDescriptor; + distance: number; +} + +export const findNearestNeighbors = ( + target: GeometryDescriptor, + candidates: GeometryCandidate[], + k: number, +): NearestNeighbor[] => + candidates + .map(({ item, descriptor }) => ({ + item, + descriptor, + distance: computeGeometryDistance(target, descriptor).total, + })) + .sort((a, b) => a.distance - b.distance) + .slice(0, k); diff --git a/src/domain/grid/Grid.test.ts b/src/domain/grid/Grid.test.ts new file mode 100644 index 0000000..47d6910 --- /dev/null +++ b/src/domain/grid/Grid.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest'; +import { parseGrid } from './Grid.ts'; + +describe('parseGrid', () => { + it('normalizes numbers and stars in ascending order', () => { + const grid = parseGrid({ numbers: [42, 3, 19, 7, 31], stars: [9, 2] }); + + expect(grid.numbers).toEqual([3, 7, 19, 31, 42]); + expect(grid.stars).toEqual([2, 9]); + }); + + it('is invariant to input order', () => { + const a = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + const b = parseGrid({ numbers: [5, 4, 3, 2, 1], stars: [2, 1] }); + + expect(a).toEqual(b); + }); + + it('rejects duplicate numbers', () => { + expect(() => parseGrid({ numbers: [1, 1, 3, 4, 5], stars: [1, 2] })).toThrow(); + }); + + it('rejects duplicate stars', () => { + expect(() => parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 1] })).toThrow(); + }); + + it('rejects numbers out of the 1..50 range', () => { + expect(() => parseGrid({ numbers: [0, 2, 3, 4, 5], stars: [1, 2] })).toThrow(); + expect(() => parseGrid({ numbers: [1, 2, 3, 4, 51], stars: [1, 2] })).toThrow(); + }); + + it('rejects stars out of the 1..12 range', () => { + expect(() => parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [0, 2] })).toThrow(); + expect(() => parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 13] })).toThrow(); + }); + + it('rejects the wrong count of numbers or stars', () => { + expect(() => parseGrid({ numbers: [1, 2, 3, 4], stars: [1, 2] })).toThrow(); + expect(() => parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1] })).toThrow(); + }); +}); diff --git a/src/domain/grid/Grid.ts b/src/domain/grid/Grid.ts new file mode 100644 index 0000000..6ddea7b --- /dev/null +++ b/src/domain/grid/Grid.ts @@ -0,0 +1,29 @@ +import { z } from 'zod'; + +export interface Grid { + numbers: [number, number, number, number, number]; + stars: [number, number]; +} + +const distinctSortedTuple = (min: number, max: number, count: number) => + z + .array(z.number().int().min(min).max(max)) + .length(count) + .refine((values) => new Set(values).size === count, { + message: `Expected ${count} distinct values between ${min} and ${max}`, + }) + .transform((values) => [...values].sort((a, b) => a - b)); + +export const gridSchema = z + .object({ + numbers: distinctSortedTuple(1, 50, 5), + stars: distinctSortedTuple(1, 12, 2), + }) + .transform( + ({ numbers, stars }): Grid => ({ + numbers: numbers as Grid['numbers'], + stars: stars as Grid['stars'], + }), + ); + +export const parseGrid = (input: unknown): Grid => gridSchema.parse(input); diff --git a/src/domain/random/seededRandom.test.ts b/src/domain/random/seededRandom.test.ts new file mode 100644 index 0000000..3bd7655 --- /dev/null +++ b/src/domain/random/seededRandom.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { createSeededRandom } from './seededRandom.ts'; + +describe('createSeededRandom', () => { + it('produces the same sequence for the same seed', () => { + const a = createSeededRandom(42); + const b = createSeededRandom(42); + + const sequenceA = Array.from({ length: 10 }, () => a()); + const sequenceB = Array.from({ length: 10 }, () => b()); + + expect(sequenceA).toEqual(sequenceB); + }); + + it('produces a different sequence for a different seed', () => { + const a = createSeededRandom(1); + const b = createSeededRandom(2); + + const sequenceA = Array.from({ length: 10 }, () => a()); + const sequenceB = Array.from({ length: 10 }, () => b()); + + expect(sequenceA).not.toEqual(sequenceB); + }); + + it('produces values in [0, 1)', () => { + const random = createSeededRandom(7); + + for (let i = 0; i < 1000; i += 1) { + const value = random(); + expect(value).toBeGreaterThanOrEqual(0); + expect(value).toBeLessThan(1); + } + }); +}); diff --git a/src/domain/random/seededRandom.ts b/src/domain/random/seededRandom.ts new file mode 100644 index 0000000..5fdfeeb --- /dev/null +++ b/src/domain/random/seededRandom.ts @@ -0,0 +1,14 @@ +/** + * mulberry32 - small, fast, deterministic PRNG. Same seed always produces + * the same sequence, which is what reproducible experiments (V2) need. + */ +export const createSeededRandom = (seed: number): (() => number) => { + let state = seed >>> 0; + + return () => { + state = (state + 0x6d2b79f5) | 0; + let t = Math.imul(state ^ (state >>> 15), 1 | state); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +}; diff --git a/src/domain/scoring/TemporalWindow.test.ts b/src/domain/scoring/TemporalWindow.test.ts new file mode 100644 index 0000000..2c28b63 --- /dev/null +++ b/src/domain/scoring/TemporalWindow.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import type { Draw } from '../draw/Draw.ts'; +import { filterByWindow, latestDrawDate } from './TemporalWindow.ts'; + +const drawOn = (date: string): Draw => ({ + id: date, + date, + numbers: [1, 2, 3, 4, 5], + stars: [1, 2], + source: 'manual', +}); + +describe('filterByWindow', () => { + const draws = [drawOn('2020-01-01'), drawOn('2023-01-01'), drawOn('2025-06-01'), drawOn('2026-01-01')]; + const referenceDate = latestDrawDate(draws); + + it('returns every draw for the "all" window', () => { + expect(filterByWindow(draws, 'all', referenceDate)).toHaveLength(4); + }); + + it('keeps only draws within N years of the reference date', () => { + const oneYear = filterByWindow(draws, 1, referenceDate); + expect(oneYear.map((d) => d.date)).toEqual(['2025-06-01', '2026-01-01']); + }); + + it('has no privileged window - a 6-year window is just another cutoff', () => { + const sixYears = filterByWindow(draws, 6, referenceDate); + expect(sixYears).toHaveLength(4); + }); +}); + +describe('latestDrawDate', () => { + it('finds the most recent draw date', () => { + const draws = [drawOn('2020-01-01'), drawOn('2026-01-01'), drawOn('2023-01-01')]; + expect(latestDrawDate(draws).toISOString().slice(0, 10)).toBe('2026-01-01'); + }); +}); diff --git a/src/domain/scoring/TemporalWindow.ts b/src/domain/scoring/TemporalWindow.ts new file mode 100644 index 0000000..dd25cd1 --- /dev/null +++ b/src/domain/scoring/TemporalWindow.ts @@ -0,0 +1,24 @@ +import type { Draw } from '../draw/Draw.ts'; + +export type TemporalWindow = 1 | 3 | 6 | 12 | 25 | 50 | 'all'; + +export const TEMPORAL_WINDOWS: TemporalWindow[] = [1, 3, 6, 12, 25, 50, 'all']; + +export const filterByWindow = ( + draws: Draw[], + window: TemporalWindow, + referenceDate: Date, +): Draw[] => { + if (window === 'all') return draws; + + const cutoff = new Date(referenceDate); + cutoff.setFullYear(cutoff.getFullYear() - window); + + return draws.filter((draw) => new Date(draw.date) >= cutoff); +}; + +export const latestDrawDate = (draws: Draw[]): Date => + draws.reduce((latest, draw) => { + const drawDate = new Date(draw.date); + return drawDate > latest ? drawDate : latest; + }, new Date(0)); diff --git a/src/domain/scoring/evaluateGrid.test.ts b/src/domain/scoring/evaluateGrid.test.ts new file mode 100644 index 0000000..2e8be72 --- /dev/null +++ b/src/domain/scoring/evaluateGrid.test.ts @@ -0,0 +1,57 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { parseGrid } from '../grid/Grid.ts'; +import { classifyReading, evaluateGrid } from './evaluateGrid.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +describe('evaluateGrid', () => { + it('produces four 0-100 scores, each with non-empty factors', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + const scores = evaluateGrid(grid, history); + + for (const score of Object.values(scores)) { + expect(score.value).toBeGreaterThanOrEqual(0); + expect(score.value).toBeLessThanOrEqual(100); + expect(score.factors.length).toBeGreaterThan(0); + } + }); + + it('is deterministic for the same grid and history', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(evaluateGrid(grid, history)).toEqual(evaluateGrid(grid, history)); + }); + + it('rates a real historical draw as structurally closer than an arbitrary far-out grid', () => { + const realDraw = history[0]; + const commonGrid = parseGrid({ numbers: [...realDraw.numbers], stars: [...realDraw.stars] }); + const farOutGrid = parseGrid({ numbers: [1, 2, 3, 4, 5], stars: [1, 2] }); + + const commonScores = evaluateGrid(commonGrid, history); + const farOutScores = evaluateGrid(farOutGrid, history); + + expect(commonScores.structure.value).toBeGreaterThan(farOutScores.structure.value); + }); + + it('classifies a common-structure, low-originality grid as "classique"', () => { + const realDraw = history[0]; + const grid = parseGrid({ numbers: [...realDraw.numbers], stars: [...realDraw.stars] }); + + const scores = evaluateGrid(grid, history); + + expect(classifyReading(scores)).toBe( + scores.originality.value >= 50 ? 'interessante' : 'classique', + ); + }); + + it('handles an empty history without throwing', () => { + const grid = parseGrid({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + expect(() => evaluateGrid(grid, [])).not.toThrow(); + }); +}); diff --git a/src/domain/scoring/evaluateGrid.ts b/src/domain/scoring/evaluateGrid.ts new file mode 100644 index 0000000..e50a412 --- /dev/null +++ b/src/domain/scoring/evaluateGrid.ts @@ -0,0 +1,140 @@ +import type { Draw } from '../draw/Draw.ts'; +import type { Grid } from '../grid/Grid.ts'; +import { extractFeatures } from '../features/FeatureExtractor.ts'; +import { buildGeometryDescriptor } from '../geometry/GeometryDescriptor.ts'; +import { computeGeometryDistance } from '../geometry/GeometryDistance.ts'; +import { findNearestNeighbors } from '../geometry/nearestNeighbors.ts'; +import { TEMPORAL_WINDOWS, filterByWindow, latestDrawDate } from './TemporalWindow.ts'; + +export interface ScoreFactor { + label: string; + value: number; +} + +export interface Score { + value: number; + factors: ScoreFactor[]; +} + +export interface EvaluationScores { + structure: Score; + originality: Score; + temporal: Score; + confidence: Score; +} + +const NEIGHBOR_SAMPLE_SIZE = 20; + +const mean = (values: number[]): number => + values.length === 0 ? 0 : values.reduce((acc, value) => acc + value, 0) / values.length; + +const standardDeviation = (values: number[]): number => { + if (values.length === 0) return 0; + const average = mean(values); + return Math.sqrt(mean(values.map((value) => (value - average) ** 2))); +}; + +const percentileRank = (value: number, population: number[]): number => + population.length === 0 + ? 50 + : (population.filter((candidate) => candidate < value).length / population.length) * 100; + +const toScoreValue = (ratio: number): number => Math.round(100 * Math.min(Math.max(ratio, 0), 1)); + +export const evaluateGrid = (grid: Grid, history: Draw[]): EvaluationScores => { + const descriptor = buildGeometryDescriptor(grid); + const historyEntries = history.map((draw) => ({ + item: draw, + descriptor: buildGeometryDescriptor({ numbers: draw.numbers, stars: draw.stars }), + })); + + const neighbors = findNearestNeighbors(descriptor, historyEntries, NEIGHBOR_SAMPLE_SIZE); + const neighborDistances = neighbors.map((neighbor) => neighbor.distance); + const meanNeighborDistance = mean(neighborDistances); + + const historySums = historyEntries.map(({ descriptor: entry }) => entry.sum); + const historyRanges = historyEntries.map(({ descriptor: entry }) => entry.range); + const sameDecadeSignatureCount = historyEntries.filter(({ descriptor: entry }) => + entry.decadeBuckets.every((count, index) => count === descriptor.decadeBuckets[index]), + ).length; + const sameParityCount = historyEntries.filter( + ({ descriptor: entry }) => entry.oddCount === descriptor.oddCount, + ).length; + + const structure: Score = { + value: toScoreValue(1 - meanNeighborDistance), + factors: [ + { label: 'meanNeighborDistance', value: meanNeighborDistance }, + { label: 'sumPercentile', value: percentileRank(descriptor.sum, historySums) }, + { label: 'amplitudePercentile', value: percentileRank(descriptor.range, historyRanges) }, + { + label: 'decadeSignatureMatchRate', + value: history.length === 0 ? 0 : (sameDecadeSignatureCount / history.length) * 100, + }, + { + label: 'parityMatchRate', + value: history.length === 0 ? 0 : (sameParityCount / history.length) * 100, + }, + ], + }; + + const features = extractFeatures(grid).values; + const humanPatternRatio = + 0.25 * Math.min(features.consecutivePairsCount / 4, 1) + + 0.25 * Math.min(features.sameUnitsPairsCount / 10, 1) + + 0.25 * Math.min(features.multiplesOfFiveCount / 5, 1) + + 0.25 * Math.min((5 - features.aboveThirtyOneCount) / 5, 1); + + const originality: Score = { + value: toScoreValue(1 - humanPatternRatio), + factors: [ + { label: 'consecutivePairsCount', value: features.consecutivePairsCount }, + { label: 'sameUnitsPairsCount', value: features.sameUnitsPairsCount }, + { label: 'multiplesOfFiveCount', value: features.multiplesOfFiveCount }, + { label: 'aboveThirtyOneCount', value: features.aboveThirtyOneCount }, + ], + }; + + const referenceDate = latestDrawDate(history); + const windowFactors = TEMPORAL_WINDOWS.map((window) => { + const windowDraws = filterByWindow(history, window, referenceDate); + const windowDistances = windowDraws.map( + (draw) => + computeGeometryDistance( + descriptor, + buildGeometryDescriptor({ numbers: draw.numbers, stars: draw.stars }), + ).total, + ); + return { label: `window_${window}`, value: toScoreValue(1 - mean(windowDistances)) }; + }); + + const temporal: Score = { + value: Math.round(mean(windowFactors.map((factor) => factor.value))), + factors: windowFactors, + }; + + const neighborDistanceStdDev = standardDeviation(neighborDistances); + const coefficientOfVariation = + meanNeighborDistance === 0 ? 0 : neighborDistanceStdDev / meanNeighborDistance; + + const confidence: Score = { + value: toScoreValue(1 - coefficientOfVariation), + factors: [ + { label: 'sampleSize', value: history.length }, + { label: 'neighborMeanDistance', value: meanNeighborDistance }, + { label: 'neighborDistanceStdDev', value: neighborDistanceStdDev }, + ], + }; + + return { structure, originality, temporal, confidence }; +}; + +export type ReadingMatrixLabel = 'classique' | 'interessante' | 'atypique' | 'tres-atypique'; + +export const classifyReading = (scores: EvaluationScores): ReadingMatrixLabel => { + const isCommonStructure = scores.structure.value >= 50; + const isHighOriginality = scores.originality.value >= 50; + + if (isCommonStructure) return isHighOriginality ? 'interessante' : 'classique'; + return isHighOriginality ? 'tres-atypique' : 'atypique'; +}; diff --git a/src/domain/strategy/Strategy.ts b/src/domain/strategy/Strategy.ts new file mode 100644 index 0000000..a333f68 --- /dev/null +++ b/src/domain/strategy/Strategy.ts @@ -0,0 +1,42 @@ +/** + * Rule kinds cover every V2-spec category except "zone géométrique" - + * that one targets a SpatialEmbedding region, which doesn't exist as a + * real clustered thing before V3's DiscoveryModel. + * + * Scoring kinds (number-frequency, above-31, repeat-from-previous, + * recency, star-frequency) contribute a weighted [0,1] preference per + * number/star; a negative weight flips the preference (e.g. + * number-frequency with weight -1 prefers rare numbers instead of + * frequent ones - fréquences and rareté are the same rule, signed). + * Constraint kinds (decade-spread, sum-range, parity-target) target a + * property of the whole 5-number set and are applied as a bounded + * greedy adjustment after the initial scored selection. + */ +export type StrategyRuleKind = + | 'number-frequency' + | 'above-31' + | 'repeat-from-previous' + | 'recency' + | 'decade-spread' + | 'sum-range' + | 'parity-target' + | 'star-frequency'; + +export interface StrategyRuleParams { + min?: number; + max?: number; + oddCount?: number; +} + +export interface StrategyRule { + kind: StrategyRuleKind; + weight?: number; + params?: StrategyRuleParams; +} + +export interface Strategy { + id: string; + name: string; + rules: StrategyRule[]; + seed: number; +} diff --git a/src/domain/strategy/proposeGrid.test.ts b/src/domain/strategy/proposeGrid.test.ts new file mode 100644 index 0000000..b4ac9a9 --- /dev/null +++ b/src/domain/strategy/proposeGrid.test.ts @@ -0,0 +1,97 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { proposeGrid } from './proposeGrid.ts'; +import type { Strategy } from './Strategy.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const history = parseFdjCsv(readFileSync(REAL_CSV_PATH, 'utf8')); + +const strategyOf = (rules: Strategy['rules'], seed = 42): Strategy => ({ + id: 'test-strategy', + name: 'Test', + rules, + seed, +}); + +describe('proposeGrid', () => { + it('is deterministic for the same seed, strategy, and history', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }]); + + expect(proposeGrid(strategy, history)).toEqual(proposeGrid(strategy, history)); + }); + + it('produces a different grid for a different seed (almost always)', () => { + const withoutRules = strategyOf([]); + + const gridA = proposeGrid({ ...withoutRules, seed: 1 }, history); + const gridB = proposeGrid({ ...withoutRules, seed: 2 }, history); + + expect(gridA).not.toEqual(gridB); + }); + + it('always produces a valid Grid regardless of rule combination', () => { + const strategy = strategyOf([ + { kind: 'number-frequency', weight: 1 }, + { kind: 'above-31', weight: 0.5 }, + { kind: 'decade-spread' }, + { kind: 'sum-range', params: { min: 100, max: 150 } }, + { kind: 'parity-target', params: { oddCount: 3 } }, + { kind: 'star-frequency' }, + ]); + + const grid = proposeGrid(strategy, history); + + expect(new Set(grid.numbers).size).toBe(5); + expect(new Set(grid.stars).size).toBe(2); + }); + + it('above-31 rule with strong weight favors numbers greater than 31', () => { + const strategy = strategyOf([{ kind: 'above-31', weight: 10 }]); + + const grid = proposeGrid(strategy, history); + + expect(grid.numbers.every((n) => n > 31)).toBe(true); + }); + + it('a negative weight flips the frequency preference toward rare numbers', () => { + const frequent = strategyOf([{ kind: 'number-frequency', weight: 1 }]); + const rare = strategyOf([{ kind: 'number-frequency', weight: -1 }]); + + expect(proposeGrid(frequent, history)).not.toEqual(proposeGrid(rare, history)); + }); + + it('decade-spread produces one number per decade when feasible', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }, { kind: 'decade-spread' }]); + + const grid = proposeGrid(strategy, history); + const buckets = new Set(grid.numbers.map((n) => Math.min(Math.floor((n - 1) / 10), 4))); + + expect(buckets.size).toBe(5); + }); + + it('sum-range constrains the total sum into the requested range', () => { + const strategy = strategyOf([{ kind: 'sum-range', params: { min: 100, max: 120 } }]); + + const grid = proposeGrid(strategy, history); + const sum = grid.numbers.reduce((acc, n) => acc + n, 0); + + expect(sum).toBeGreaterThanOrEqual(100); + expect(sum).toBeLessThanOrEqual(120); + }); + + it('parity-target hits the exact requested odd count when feasible', () => { + const strategy = strategyOf([{ kind: 'parity-target', params: { oddCount: 5 } }]); + + const grid = proposeGrid(strategy, history); + + expect(grid.numbers.every((n) => n % 2 !== 0)).toBe(true); + }); + + it('handles an empty history without throwing', () => { + const strategy = strategyOf([{ kind: 'number-frequency' }, { kind: 'recency' }]); + + expect(() => proposeGrid(strategy, [])).not.toThrow(); + }); +}); diff --git a/src/domain/strategy/proposeGrid.ts b/src/domain/strategy/proposeGrid.ts new file mode 100644 index 0000000..8b7cf00 --- /dev/null +++ b/src/domain/strategy/proposeGrid.ts @@ -0,0 +1,204 @@ +import type { Draw } from '../draw/Draw.ts'; +import type { Grid } from '../grid/Grid.ts'; +import { parseGrid } from '../grid/Grid.ts'; +import { createSeededRandom } from '../random/seededRandom.ts'; +import type { Strategy, StrategyRule, StrategyRuleParams } from './Strategy.ts'; + +const range = (min: number, max: number): number[] => + Array.from({ length: max - min + 1 }, (_, i) => min + i); + +const decadeBucketOf = (n: number): number => Math.min(Math.floor((n - 1) / 10), 4); + +const countByValue = (values: number[], min: number, max: number): Record => { + const counts: Record = {}; + for (let v = min; v <= max; v += 1) counts[v] = 0; + for (const v of values) counts[v] += 1; + return counts; +}; + +const normalizeToUnit = (values: Record): Record => { + const max = Math.max(1, ...Object.values(values)); + const normalized: Record = {}; + for (const [key, value] of Object.entries(values)) normalized[Number(key)] = value / max; + return normalized; +}; + +const sortedByDateDescending = (draws: Draw[]): Draw[] => + [...draws].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + +const gapsSinceLastSeen = ( + sortedDescending: Draw[], + min: number, + max: number, + pick: (draw: Draw) => readonly number[], +): Record => { + const gaps: Record = {}; + for (let n = min; n <= max; n += 1) gaps[n] = Number.POSITIVE_INFINITY; + + for (const [index, draw] of sortedDescending.entries()) { + for (const n of pick(draw)) { + if (!Number.isFinite(gaps[n])) gaps[n] = index; + } + } + + return gaps; +}; + +const scoreNumbers = (rules: StrategyRule[], history: Draw[]): Record => { + const scores: Record = {}; + for (let n = 1; n <= 50; n += 1) scores[n] = 0; + + const needsFrequency = rules.some((rule) => rule.kind === 'number-frequency'); + const needsRepeat = rules.some((rule) => rule.kind === 'repeat-from-previous'); + const needsRecency = rules.some((rule) => rule.kind === 'recency'); + + const sortedDescending = needsRepeat || needsRecency ? sortedByDateDescending(history) : []; + const mostRecentDraw = sortedDescending[0]; + const normalizedFrequency = needsFrequency + ? normalizeToUnit(countByValue(history.flatMap((draw) => draw.numbers), 1, 50)) + : {}; + const gaps = needsRecency ? gapsSinceLastSeen(sortedDescending, 1, 50, (draw) => draw.numbers) : {}; + + for (const rule of rules) { + const weight = rule.weight ?? 1; + if (rule.kind === 'number-frequency') { + for (let n = 1; n <= 50; n += 1) scores[n] += weight * normalizedFrequency[n]; + } else if (rule.kind === 'above-31') { + for (let n = 1; n <= 50; n += 1) scores[n] += weight * (n > 31 ? 1 : 0); + } else if (rule.kind === 'repeat-from-previous') { + for (let n = 1; n <= 50; n += 1) { + scores[n] += weight * (mostRecentDraw?.numbers.includes(n) ? 1 : 0); + } + } else if (rule.kind === 'recency') { + for (let n = 1; n <= 50; n += 1) scores[n] += weight * (1 / (1 + gaps[n])); + } + } + + return scores; +}; + +const scoreStars = (rules: StrategyRule[], history: Draw[]): Record => { + const scores: Record = {}; + for (let s = 1; s <= 12; s += 1) scores[s] = 0; + + if (!rules.some((rule) => rule.kind === 'star-frequency')) return scores; + + const normalizedFrequency = normalizeToUnit(countByValue(history.flatMap((draw) => draw.stars), 1, 12)); + + for (const rule of rules) { + if (rule.kind === 'star-frequency') { + const weight = rule.weight ?? 1; + for (let s = 1; s <= 12; s += 1) scores[s] += weight * normalizedFrequency[s]; + } + } + + return scores; +}; + +const topN = ( + scores: Record, + min: number, + max: number, + count: number, + random: () => number, +): number[] => + range(min, max) + .map((n) => ({ n, jitteredScore: scores[n] + random() * 1e-6 })) + .sort((a, b) => b.jitteredScore - a.jitteredScore) + .slice(0, count) + .map((entry) => entry.n) + .sort((a, b) => a - b); + +const applyDecadeSpread = (numbers: number[], scores: Record): number[] => { + let current = [...numbers]; + + for (let pass = 0; pass < 10; pass += 1) { + const buckets = [0, 0, 0, 0, 0]; + for (const n of current) buckets[decadeBucketOf(n)] += 1; + + const overIndex = buckets.findIndex((count) => count >= 2); + const underIndex = buckets.findIndex((count) => count === 0); + if (overIndex === -1 || underIndex === -1) break; + + const worstInOverDecade = current + .filter((n) => decadeBucketOf(n) === overIndex) + .sort((a, b) => scores[a] - scores[b])[0]; + + const underDecadeMin = underIndex * 10 + 1; + const underDecadeMax = Math.min(underIndex * 10 + 10, 50); + const bestReplacement = range(underDecadeMin, underDecadeMax) + .filter((n) => !current.includes(n)) + .sort((a, b) => scores[b] - scores[a])[0]; + + if (bestReplacement === undefined) break; + current = current.map((n) => (n === worstInOverDecade ? bestReplacement : n)); + } + + return current; +}; + +const applySumRange = (numbers: number[], scores: Record, params: StrategyRuleParams): number[] => { + const min = params.min ?? 0; + const max = params.max ?? 250; + let current = [...numbers]; + + for (let pass = 0; pass < 20; pass += 1) { + const sum = current.reduce((acc, n) => acc + n, 0); + if (sum >= min && sum <= max) break; + + const needsIncrease = sum < min; + const worst = [...current].sort((a, b) => scores[a] - scores[b])[0]; + const replacement = range(1, 50) + .filter((n) => !current.includes(n) && (needsIncrease ? n > worst : n < worst)) + .sort((a, b) => scores[b] - scores[a])[0]; + + if (replacement === undefined) break; + current = current.map((n) => (n === worst ? replacement : n)); + } + + return current; +}; + +const applyParityTarget = ( + numbers: number[], + scores: Record, + params: StrategyRuleParams, +): number[] => { + const targetOddCount = params.oddCount ?? 3; + let current = [...numbers]; + + for (let pass = 0; pass < 10; pass += 1) { + const currentOddCount = current.filter((n) => n % 2 !== 0).length; + if (currentOddCount === targetOddCount) break; + + const needsMoreOdd = currentOddCount < targetOddCount; + const worst = [...current].sort((a, b) => scores[a] - scores[b]).find((n) => (needsMoreOdd ? n % 2 === 0 : n % 2 !== 0)); + if (worst === undefined) break; + + const replacement = range(1, 50) + .filter((n) => !current.includes(n) && (needsMoreOdd ? n % 2 !== 0 : n % 2 === 0)) + .sort((a, b) => scores[b] - scores[a])[0]; + + if (replacement === undefined) break; + current = current.map((n) => (n === worst ? replacement : n)); + } + + return current; +}; + +export const proposeGrid = (strategy: Strategy, history: Draw[]): Grid => { + const random = createSeededRandom(strategy.seed); + const numberScores = scoreNumbers(strategy.rules, history); + const starScores = scoreStars(strategy.rules, history); + + let numbers = topN(numberScores, 1, 50, 5, random); + const stars = topN(starScores, 1, 12, 2, random); + + for (const rule of strategy.rules) { + if (rule.kind === 'decade-spread') numbers = applyDecadeSpread(numbers, numberScores); + if (rule.kind === 'sum-range') numbers = applySumRange(numbers, numberScores, rule.params ?? {}); + if (rule.kind === 'parity-target') numbers = applyParityTarget(numbers, numberScores, rule.params ?? {}); + } + + return parseGrid({ numbers, stars }); +}; diff --git a/src/index.css b/src/index.css index 0f0ad22..9db6410 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,50 @@ :root { - --text: #6b6375; - --bg: #fff; - --border: #e5e4e7; + /* Dark-first: this is the product's primary identity (see ADR/V4 intent - + data art / observatory), overridden below for prefers-color-scheme: light. */ + --bg: #0a0c11; + --surface: #12151d; + --surface-raised: #171b25; + --border: #262b38; + --border-strong: #363c4d; + --text: #e6e8ee; + --text-muted: #8b92a5; - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --accent-structure: #7dd3fc; + --accent-originality: #fbbf6b; + --accent-temporal: #b9a3fb; + --accent-confidence: #5fe0b5; + --accent-primary: var(--accent-structure); + --accent-reference: #ff5eae; + --danger: #f2707d; + --danger-surface: rgba(242, 112, 125, 0.1); - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; + --sans: 'Inter', system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; + + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.5rem; + --space-6: 2rem; + --space-7: 3rem; + + --text-xs: 0.8rem; + --text-sm: 0.9rem; + --text-base: 1rem; + --text-lg: 1.15rem; + --text-xl: 1.4rem; + --text-2xl: 1.85rem; + --text-3xl: 2.4rem; + + --radius-sm: 6px; + --radius-md: 10px; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.35); + + font: var(--text-base) / 1.55 var(--sans); + letter-spacing: 0.1px; + color-scheme: dark light; color: var(--text); background: var(--bg); font-synthesis: none; @@ -16,30 +53,138 @@ -moz-osx-font-smoothing: grayscale; @media (max-width: 1024px) { - font-size: 16px; + font-size: 15px; } } -@media (prefers-color-scheme: dark) { +@media (prefers-color-scheme: light) { :root { - --text: #9ca3af; - --bg: #16171d; - --border: #2e303a; + --bg: #f6f7fb; + --surface: #ffffff; + --surface-raised: #ffffff; + --border: #e2e5ec; + --border-strong: #c9ced9; + --text: #20232e; + --text-muted: #666e80; + + --accent-structure: #3a7ecf; + --accent-originality: #c9791f; + --accent-temporal: #7250c9; + --accent-confidence: #12886b; + --accent-reference: #c2185b; + --danger: #d4394b; + --danger-surface: rgba(212, 57, 75, 0.08); + + --shadow-sm: 0 1px 2px rgba(20, 24, 38, 0.06); + --shadow-md: 0 8px 24px rgba(20, 24, 38, 0.08); } } +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + #root { - width: 1126px; + width: 1200px; max-width: 100%; margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); min-height: 100svh; display: flex; flex-direction: column; - box-sizing: border-box; } -body { - margin: 0; +h1, +h2, +h3 { + font-weight: 650; + letter-spacing: -0.01em; + line-height: 1.25; +} + +h1 { + font-size: var(--text-2xl); +} + +h2 { + font-size: var(--text-xl); +} + +h3 { + font-size: var(--text-lg); +} + +a { + color: var(--accent-primary); +} + +fieldset { + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: var(--space-4); +} + +legend { + padding: 0 var(--space-2); + color: var(--text-muted); + font-size: var(--text-sm); +} + +label { + font-size: var(--text-sm); + color: var(--text-muted); +} + +input, +select { + font: inherit; + color: var(--text); + background: var(--surface); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + padding: var(--space-2) var(--space-3); + transition: border-color 0.15s ease; +} + +input:focus-visible, +select:focus-visible, +button:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 1px; +} + +input:hover, +select:hover { + border-color: var(--border-strong); +} + +button { + font: inherit; + font-weight: 600; + color: #0a0c11; + background: var(--accent-primary); + border: none; + border-radius: var(--radius-sm); + padding: var(--space-2) var(--space-4); + cursor: pointer; + transition: opacity 0.15s ease; +} + +button:hover { + opacity: 0.88; +} + +button:active { + opacity: 0.75; +} + +table { + border-collapse: collapse; +} + +::selection { + background: color-mix(in srgb, var(--accent-primary) 35%, transparent); } diff --git a/src/infrastructure/csv/parseFdjCsv.test.ts b/src/infrastructure/csv/parseFdjCsv.test.ts new file mode 100644 index 0000000..0b682ed --- /dev/null +++ b/src/infrastructure/csv/parseFdjCsv.test.ts @@ -0,0 +1,46 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { parseFdjCsv } from './parseFdjCsv.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); + +describe('parseFdjCsv', () => { + it('parses a minimal FDJ-shaped CSV into Draws', () => { + const csv = [ + 'annee_numero_de_tirage;date_de_tirage;boules_gagnantes_en_ordre_croissant;etoiles_gagnantes_en_ordre_croissant', + '26063;07/08/2026;-26-29-35-38-47-;-1-2-', + '26062;04/08/2026;-25-30-34-46-50-;-1-12-', + ].join('\n'); + + const draws = parseFdjCsv(csv); + + expect(draws).toEqual([ + { id: '26063', date: '2026-08-07', numbers: [26, 29, 35, 38, 47], stars: [1, 2], source: 'fdj-csv' }, + { id: '26062', date: '2026-08-04', numbers: [25, 30, 34, 46, 50], stars: [1, 12], source: 'fdj-csv' }, + ]); + }); + + it('throws on a malformed row (invalid grid)', () => { + const csv = [ + 'annee_numero_de_tirage;date_de_tirage;boules_gagnantes_en_ordre_croissant;etoiles_gagnantes_en_ordre_croissant', + '1;01/01/2024;-1-1-3-4-5-;-1-2-', + ].join('\n'); + + expect(() => parseFdjCsv(csv)).toThrow(); + }); + + it('parses the full real FDJ export without throwing', () => { + const csvText = readFileSync(REAL_CSV_PATH, 'utf8'); + + const draws = parseFdjCsv(csvText); + + expect(draws.length).toBeGreaterThan(600); + for (const draw of draws) { + expect(draw.numbers).toHaveLength(5); + expect(draw.stars).toHaveLength(2); + expect(draw.source).toBe('fdj-csv'); + expect(draw.date).toMatch(/^\d{4}-\d{2}-\d{2}$/); + } + }); +}); diff --git a/src/infrastructure/csv/parseFdjCsv.ts b/src/infrastructure/csv/parseFdjCsv.ts new file mode 100644 index 0000000..1de13fc --- /dev/null +++ b/src/infrastructure/csv/parseFdjCsv.ts @@ -0,0 +1,45 @@ +import type { Draw } from '../../domain/draw/Draw.ts'; +import { parseGrid } from '../../domain/grid/Grid.ts'; + +const DASH_NUMBER = /-(\d+)/g; + +const parseDashList = (value: string): number[] => + [...value.matchAll(DASH_NUMBER)].map((match) => Number(match[1])); + +const parseFdjDate = (value: string): string => { + const [day, month, year] = value.split('/'); + return `${year}-${month}-${day}`; +}; + +const columnIndex = (columns: string[], name: string): number => { + const index = columns.indexOf(name); + if (index === -1) throw new Error(`Missing FDJ CSV column: ${name}`); + return index; +}; + +export const parseFdjCsv = (csvText: string): Draw[] => { + const lines = csvText.split(/\r?\n/).filter((line) => line.trim().length > 0); + const [headerLine, ...rows] = lines; + const columns = headerLine.split(';'); + + const idIndex = columnIndex(columns, 'annee_numero_de_tirage'); + const dateIndex = columnIndex(columns, 'date_de_tirage'); + const numbersIndex = columnIndex(columns, 'boules_gagnantes_en_ordre_croissant'); + const starsIndex = columnIndex(columns, 'etoiles_gagnantes_en_ordre_croissant'); + + return rows.map((row) => { + const cells = row.split(';'); + const grid = parseGrid({ + numbers: parseDashList(cells[numbersIndex]), + stars: parseDashList(cells[starsIndex]), + }); + + return { + id: cells[idIndex], + date: parseFdjDate(cells[dateIndex]), + numbers: grid.numbers, + stars: grid.stars, + source: 'fdj-csv', + } satisfies Draw; + }); +}; diff --git a/src/infrastructure/repositories/CsvDrawRepository.test.ts b/src/infrastructure/repositories/CsvDrawRepository.test.ts new file mode 100644 index 0000000..e643ff1 --- /dev/null +++ b/src/infrastructure/repositories/CsvDrawRepository.test.ts @@ -0,0 +1,45 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createCsvDrawRepository } from './CsvDrawRepository.ts'; + +const REAL_CSV_PATH = resolve(process.cwd(), 'public/results/euromillions_202002.csv'); +const csvText = readFileSync(REAL_CSV_PATH, 'utf8'); + +describe('createCsvDrawRepository', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('fetches the CSV once and reuses it across calls', async () => { + const fetchSpy = vi.fn(() => Promise.resolve(new Response(csvText))); + vi.stubGlobal('fetch', fetchSpy); + const repository = createCsvDrawRepository('/results/test.csv'); + + const all = await repository.getAll(); + const latest = await repository.getLatest(5); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(all.length).toBeGreaterThan(600); + expect(latest).toHaveLength(5); + }); + + it('getLatest returns draws ordered from most to least recent', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(csvText))); + const repository = createCsvDrawRepository('/results/test.csv'); + + const latest = await repository.getLatest(10); + + const dates = latest.map((draw) => new Date(draw.date).getTime()); + expect(dates).toEqual([...dates].sort((a, b) => b - a)); + }); + + it('getByDate finds an existing draw and returns null for an unknown date', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(csvText))); + const repository = createCsvDrawRepository('/results/test.csv'); + const [mostRecent] = await repository.getLatest(1); + + expect(await repository.getByDate(mostRecent.date)).toEqual(mostRecent); + expect(await repository.getByDate('1900-01-01')).toBeNull(); + }); +}); diff --git a/src/infrastructure/repositories/CsvDrawRepository.ts b/src/infrastructure/repositories/CsvDrawRepository.ts new file mode 100644 index 0000000..b987225 --- /dev/null +++ b/src/infrastructure/repositories/CsvDrawRepository.ts @@ -0,0 +1,32 @@ +import type { Draw } from '../../domain/draw/Draw.ts'; +import { parseFdjCsv } from '../csv/parseFdjCsv.ts'; +import type { DrawRepository } from './DrawRepository.ts'; + +export const DEFAULT_CSV_URL = `${import.meta.env.BASE_URL}results/euromillions_202002.csv`; + +export const createCsvDrawRepository = (csvUrl: string = DEFAULT_CSV_URL): DrawRepository => { + let cachedDraws: Promise | null = null; + + const loadDraws = (): Promise => { + cachedDraws ??= fetch(csvUrl) + .then((response) => response.text()) + .then((csvText) => parseFdjCsv(csvText)); + return cachedDraws; + }; + + return { + async getAll() { + return loadDraws(); + }, + async getLatest(limit) { + const draws = await loadDraws(); + return [...draws] + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .slice(0, limit); + }, + async getByDate(date) { + const draws = await loadDraws(); + return draws.find((draw) => draw.date === date) ?? null; + }, + }; +}; diff --git a/src/infrastructure/repositories/DrawRepository.ts b/src/infrastructure/repositories/DrawRepository.ts new file mode 100644 index 0000000..651f27b --- /dev/null +++ b/src/infrastructure/repositories/DrawRepository.ts @@ -0,0 +1,7 @@ +import type { Draw } from '../../domain/draw/Draw.ts'; + +export interface DrawRepository { + getLatest(limit: number): Promise; + getAll(): Promise; + getByDate(date: string): Promise; +} diff --git a/src/infrastructure/repositories/EvaluatedGridRepository.ts b/src/infrastructure/repositories/EvaluatedGridRepository.ts new file mode 100644 index 0000000..23a852c --- /dev/null +++ b/src/infrastructure/repositories/EvaluatedGridRepository.ts @@ -0,0 +1,7 @@ +import type { Grid } from '../../domain/grid/Grid.ts'; + +export interface EvaluatedGridRepository { + getLast(): Grid | null; + save(grid: Grid): void; + clear(): void; +} diff --git a/src/infrastructure/repositories/LocalStorageEvaluatedGridRepository.ts b/src/infrastructure/repositories/LocalStorageEvaluatedGridRepository.ts new file mode 100644 index 0000000..d256500 --- /dev/null +++ b/src/infrastructure/repositories/LocalStorageEvaluatedGridRepository.ts @@ -0,0 +1,23 @@ +import type { Grid } from '../../domain/grid/Grid.ts'; +import { gridSchema } from '../../domain/grid/Grid.ts'; +import type { EvaluatedGridRepository } from './EvaluatedGridRepository.ts'; + +const STORAGE_KEY = 'izeetok:evaluated-grid'; + +export const createLocalStorageEvaluatedGridRepository = (): EvaluatedGridRepository => ({ + getLast: () => { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return null; + try { + return gridSchema.parse(JSON.parse(raw)); + } catch { + return null; + } + }, + save: (grid: Grid) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(grid)); + }, + clear: () => { + localStorage.removeItem(STORAGE_KEY); + }, +}); diff --git a/src/pages/discovery/DiscoveryPage.module.scss b/src/pages/discovery/DiscoveryPage.module.scss new file mode 100644 index 0000000..611860c --- /dev/null +++ b/src/pages/discovery/DiscoveryPage.module.scss @@ -0,0 +1,112 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-6); + text-align: left; +} + +.section { + display: flex; + flex-direction: column; + gap: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface); + padding: var(--space-5); +} + +.note { + color: var(--text-muted); + font-size: var(--text-sm); +} + +.legend { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + font-size: var(--text-sm); +} + +.legendItem { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.legendSwatch { + width: 0.7rem; + height: 0.7rem; + border-radius: 50%; + display: inline-block; +} + +.familiesGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--space-4); +} + +.familyCard { + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: var(--space-4); + border-left: 3px solid var(--family-accent, var(--accent-primary)); +} + +.familyHeader { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: var(--space-2); +} + +.stabilityBadge { + font-size: var(--text-xs); + padding: 0.15rem 0.5rem; + border-radius: 999px; + font-weight: 600; +} + +.stable { + background: color-mix(in srgb, var(--accent-confidence) 20%, transparent); + color: var(--accent-confidence); +} + +.unstable { + background: var(--danger-surface); + color: var(--danger); +} + +.metrics { + display: flex; + flex-direction: column; + gap: var(--space-1); + color: var(--text-muted); + font-size: var(--text-sm); + + strong { + color: var(--text); + font-family: var(--mono); + } +} + +.table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + + th, + td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border); + text-align: left; + } + + thead th { + color: var(--text-muted); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 600; + } +} diff --git a/src/pages/discovery/DiscoveryPage.test.tsx b/src/pages/discovery/DiscoveryPage.test.tsx new file mode 100644 index 0000000..e6fb57d --- /dev/null +++ b/src/pages/discovery/DiscoveryPage.test.tsx @@ -0,0 +1,22 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import { renderWithProviders } from '../../shared/renderWithProviders.tsx'; +import { REAL_CSV_TEXT } from '../../shared/testCsvFixture.ts'; +import DiscoveryPage from './DiscoveryPage.tsx'; + +describe('DiscoveryPage', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('renders without crashing and shows families and the null-hypothesis comparison once loaded', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + expect(screen.getByRole('heading', { name: 'Discovery Engine' })).toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByTestId('family-card').length).toBeGreaterThan(0), { timeout: 10000 }); + expect(screen.getByTestId('null-hypothesis')).toBeInTheDocument(); + expect(screen.getAllByTestId('stability-badge').length).toBeGreaterThan(0); + }); +}); diff --git a/src/pages/discovery/DiscoveryPage.tsx b/src/pages/discovery/DiscoveryPage.tsx new file mode 100644 index 0000000..078d106 --- /dev/null +++ b/src/pages/discovery/DiscoveryPage.tsx @@ -0,0 +1,148 @@ +import { useMemo } from 'react'; +import type { CSSProperties } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { CartesianGrid, ResponsiveContainer, Scatter, ScatterChart, Tooltip, XAxis, YAxis } from 'recharts'; +import { drawRepository } from '../../application/drawRepository.ts'; +import type { DiscoveryConfig } from '../../application/discoverStructure.ts'; +import { discoverStructure } from '../../application/discoverStructure.ts'; +import type { Draw } from '../../domain/draw/Draw.ts'; +import styles from './DiscoveryPage.module.scss'; + +const EMPTY_DRAWS: Draw[] = []; + +const DISCOVERY_CONFIG: DiscoveryConfig = { + kCandidates: [2, 3, 4, 5, 6, 7, 8], + neighborCount: 10, + bootstrapIterations: 20, + seed: 42, +}; + +const CLUSTER_COLORS = ['#7dd3fc', '#fbbf6b', '#b9a3fb', '#5fe0b5', '#f2707d', '#93c5fd', '#fcd34d', '#c4b5fd']; + +const colorForCluster = (clusterId: string): string => { + const index = Number(clusterId) % CLUSTER_COLORS.length; + return CLUSTER_COLORS[index]; +}; + +const DiscoveryPage = () => { + const drawsQuery = useQuery({ queryKey: ['draws', 'all'], queryFn: () => drawRepository.getAll() }); + const draws = drawsQuery.data ?? EMPTY_DRAWS; + + const discovery = useMemo(() => (draws.length > 0 ? discoverStructure(draws, DISCOVERY_CONFIG) : null), [draws]); + + const scatterSeriesByCluster = useMemo(() => { + if (!discovery) return []; + const byCluster = new Map(); + for (const embedding of discovery.embeddings) { + const clusterId = embedding.clusterId ?? 'none'; + const points = byCluster.get(clusterId) ?? []; + points.push({ x: embedding.coordinates.x, y: embedding.coordinates.y, drawId: embedding.drawId }); + byCluster.set(clusterId, points); + } + return [...byCluster.entries()].sort(([a], [b]) => Number(a) - Number(b)); + }, [discovery]); + + return ( +
+

Discovery Engine

+

+ Le système recherche quelles caractéristiques structurent réellement l'historique plutôt que de fixer des + pondérations arbitraires. Hypothèse nulle : les tirages sont compatibles avec un processus aléatoire + indépendant ; toute structure détectée est comparée à cette référence. +

+ + {drawsQuery.isLoading &&

Chargement...

} + + {discovery && ( + <> +
+

Réel vs hasard

+
+

+ Score de silhouette (historique réel) : {discovery.clustering.silhouetteScore.toFixed(3)} +

+

+ Score de silhouette (historique synthétique aléatoire) :{' '} + {discovery.nullHypothesisComparison.syntheticSilhouetteScore.toFixed(3)} +

+

+ {discovery.nullHypothesisComparison.realShowsMoreStructure + ? 'Le réel montre une structure de clustering plus marquée que le hasard, sur cette mesure.' + : 'Aucun signal robuste détecté : le réel ne montre pas plus de structure que le hasard, sur cette mesure.'} +

+

+ Variance expliquée (PC1, PC2, PC3) :{' '} + {discovery.pca.explainedVariance.map((v) => `${(v * 100).toFixed(1)}%`).join(' · ')} +

+

+ Features exclues : {discovery.normalization.excludedFeatures.length} (quasi-constantes + ou redondantes, voir le modèle de normalisation) +

+
+
+ +
+

Carte (PC1 × PC2)

+

+ Chaque point est un tirage historique projeté par PCA. La couleur encode la famille (cluster) découverte, + pas une prédiction. +

+
+ {discovery.families.map((family) => ( + + + {family.label} + + ))} +
+ + + + + + + {scatterSeriesByCluster.map(([clusterId, points]) => ( + + ))} + + +
+ +
+

Familles ({discovery.clustering.k})

+
+ {discovery.families.map((family) => ( +
+
+

{family.label}

+ + {family.isStable ? 'stable' : 'instable'} + +
+

{family.description || 'Pas de trait dominant identifié'}

+

+ Fréquence : {(family.frequency * 100).toFixed(1)}% — Stabilité bootstrap :{' '} + {(family.stability * 100).toFixed(0)}% +

+
+ ))} +
+
+ + )} +
+ ); +}; + +export default DiscoveryPage; diff --git a/src/pages/draws/DrawsPage.module.scss b/src/pages/draws/DrawsPage.module.scss new file mode 100644 index 0000000..8d0914b --- /dev/null +++ b/src/pages/draws/DrawsPage.module.scss @@ -0,0 +1,57 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-4); + text-align: left; +} + +.toolbar { + display: flex; + align-items: center; + gap: var(--space-3); + color: var(--text-muted); + font-size: var(--text-sm); +} + +.tableWrapper { + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: auto; + max-height: 70vh; +} + +.table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + + th, + td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border); + text-align: left; + white-space: nowrap; + } + + thead th { + position: sticky; + top: 0; + background: var(--surface-raised); + color: var(--text-muted); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 600; + } + + tbody tr:hover { + background: var(--surface); + } + + td:nth-child(2), + td:nth-child(3), + td:nth-child(7), + td:nth-child(8) { + font-family: var(--mono); + } +} diff --git a/src/pages/draws/DrawsPage.test.tsx b/src/pages/draws/DrawsPage.test.tsx new file mode 100644 index 0000000..68dea7a --- /dev/null +++ b/src/pages/draws/DrawsPage.test.tsx @@ -0,0 +1,20 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import { renderWithProviders } from '../../shared/renderWithProviders.tsx'; +import { REAL_CSV_TEXT } from '../../shared/testCsvFixture.ts'; +import DrawsPage from './DrawsPage.tsx'; + +describe('DrawsPage', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('renders without crashing and lists the latest draws once loaded', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + expect(screen.getByRole('heading', { name: 'Tirages' })).toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByTestId('draw-row')).toHaveLength(50)); + }); +}); diff --git a/src/pages/draws/DrawsPage.tsx b/src/pages/draws/DrawsPage.tsx new file mode 100644 index 0000000..e40851f --- /dev/null +++ b/src/pages/draws/DrawsPage.tsx @@ -0,0 +1,113 @@ +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { drawRepository } from '../../application/drawRepository.ts'; +import type { Draw } from '../../domain/draw/Draw.ts'; +import { buildGeometryDescriptor } from '../../domain/geometry/GeometryDescriptor.ts'; +import { computeGeometryDistance } from '../../domain/geometry/GeometryDistance.ts'; +import styles from './DrawsPage.module.scss'; + +const DEFAULT_LIMIT = 50; + +interface DrawRow { + draw: Draw; + sum: number; + range: number; + oddCount: number; + evenCount: number; + signature: string; + distanceToPrevious: number | null; +} + +const buildRowsMostRecentFirst = (draws: Draw[]): DrawRow[] => { + const sortedAscending = [...draws].sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ); + + const rows: DrawRow[] = []; + let previousDescriptor: ReturnType | null = null; + + for (const draw of sortedAscending) { + const descriptor = buildGeometryDescriptor({ numbers: draw.numbers, stars: draw.stars }); + rows.push({ + draw, + sum: descriptor.sum, + range: descriptor.range, + oddCount: descriptor.oddCount, + evenCount: descriptor.evenCount, + signature: descriptor.decadeBuckets.join('-'), + distanceToPrevious: previousDescriptor + ? computeGeometryDistance(descriptor, previousDescriptor).total + : null, + }); + previousDescriptor = descriptor; + } + + return rows.reverse(); +}; + +const DrawsPage = () => { + const [showAll, setShowAll] = useState(false); + + const allDrawsQuery = useQuery({ + queryKey: ['draws', 'all'], + queryFn: () => drawRepository.getAll(), + }); + + const rows = useMemo(() => { + const allRows = buildRowsMostRecentFirst(allDrawsQuery.data ?? []); + return showAll ? allRows : allRows.slice(0, DEFAULT_LIMIT); + }, [allDrawsQuery.data, showAll]); + + return ( +
+

Tirages

+ + {allDrawsQuery.isLoading &&

Chargement...

} + + {allDrawsQuery.data && ( + <> +

+ {showAll ? `Historique complet (${rows.length} tirages)` : `${rows.length} derniers tirages`} + +

+
+ + + + + + + + + + + + + + + {rows.map((row) => ( + + + + + + + + + + + ))} + +
DateNumérosÉtoilesSommeAmplitudeParitéSignatureDistance au précédent
{row.draw.date}{row.draw.numbers.join(' · ')}{row.draw.stars.join(' · ')}{row.sum}{row.range} + {row.oddCount} impair / {row.evenCount} pair + {row.signature}{row.distanceToPrevious === null ? '—' : row.distanceToPrevious.toFixed(3)}
+
+ + )} +
+ ); +}; + +export default DrawsPage; diff --git a/src/pages/evaluation/EvaluationPage.module.scss b/src/pages/evaluation/EvaluationPage.module.scss new file mode 100644 index 0000000..f210b59 --- /dev/null +++ b/src/pages/evaluation/EvaluationPage.module.scss @@ -0,0 +1,105 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-6); + text-align: left; +} + +.exactMatch { + color: var(--text-muted); + font-size: var(--text-sm); +} + +.scores { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-4); +} + +.variationsNote { + color: var(--text-muted); + font-size: var(--text-sm); +} + +.variationsList { + list-style: none; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.variationItem { + display: flex; + flex-direction: column; + gap: var(--space-2); + font-size: var(--text-sm); + padding: var(--space-3) var(--space-4); + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); +} + +.variationHeader { + display: flex; + gap: var(--space-3); + align-items: baseline; + flex-wrap: wrap; + font-family: var(--mono); +} + +.variationLabel { + font-family: var(--sans); + font-weight: 600; + color: var(--text); + white-space: nowrap; +} + +.variationDescription { + margin: 0; + color: var(--text-muted); + font-size: var(--text-xs); + line-height: 1.5; +} + +.variationScores { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + font-size: var(--text-xs); + color: var(--text-muted); + padding-top: var(--space-2); + border-top: 1px solid var(--border); +} + +.variationScore { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.dotStructure, +.dotOriginality, +.dotTemporal, +.dotConfidence { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + display: inline-block; +} + +.dotStructure { + background: var(--accent-structure); +} + +.dotOriginality { + background: var(--accent-originality); +} + +.dotTemporal { + background: var(--accent-temporal); +} + +.dotConfidence { + background: var(--accent-confidence); +} diff --git a/src/pages/evaluation/EvaluationPage.test.tsx b/src/pages/evaluation/EvaluationPage.test.tsx new file mode 100644 index 0000000..ab48985 --- /dev/null +++ b/src/pages/evaluation/EvaluationPage.test.tsx @@ -0,0 +1,154 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderWithProviders } from '../../shared/renderWithProviders.tsx'; +import { REAL_CSV_TEXT } from '../../shared/testCsvFixture.ts'; +import { evaluatedGridRepository } from '../../application/evaluatedGridRepository.ts'; +import EvaluationPage from './EvaluationPage.tsx'; + +describe('EvaluationPage', () => { + afterEach(() => { + vi.unstubAllGlobals(); + localStorage.clear(); + }); + + it('renders without crashing', () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + expect(screen.getByRole('heading', { name: "Évaluation d'une grille" })).toBeInTheDocument(); + }); + + it('evaluates a filled-in grid into four scores and three variations', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + const numbers = [3, 7, 19, 31, 42]; + const stars = [2, 9]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + + await waitFor(() => expect(screen.getAllByTestId('score-card')).toHaveLength(4)); + expect(screen.getByTestId('variations').querySelectorAll('li')).toHaveLength(3); + }); + + it('flags a grid that never appeared in the available history', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + const numbers = [3, 7, 19, 31, 42]; + const stars = [2, 9]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + + await waitFor(() => + expect(screen.getByTestId('exact-match-banner')).toHaveTextContent("n'est jamais sortie"), + ); + }); + + it('flags a grid that exactly matches a past draw, with its date', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + const numbers = [21, 23, 33, 35, 47]; + const stars = [6, 7]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + + await waitFor(() => + expect(screen.getByTestId('exact-match-banner')).toHaveTextContent('déjà sortie le 2020-02-04'), + ); + }); + + it('flags when the 5 numbers match a past draw but the stars differ', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + const numbers = [21, 23, 33, 35, 47]; + const stars = [3, 4]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + + await waitFor(() => + expect(screen.getByTestId('numbers-only-match-banner')).toHaveTextContent('2020-02-04'), + ); + expect(screen.getByTestId('exact-match-banner')).toHaveTextContent("n'est jamais sortie"); + }); + + it('restores the last evaluated grid when the page is remounted', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + const first = renderWithProviders(); + + const numbers = [3, 7, 19, 31, 42]; + const stars = [2, 9]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + await waitFor(() => expect(screen.getAllByTestId('score-card')).toHaveLength(4)); + + first.unmount(); + renderWithProviders(); + + for (const [index, value] of numbers.entries()) { + expect(screen.getByTestId(`number-input-${index}`)).toHaveValue(String(value)); + } + for (const [index, value] of stars.entries()) { + expect(screen.getByTestId(`star-input-${index}`)).toHaveValue(String(value)); + } + await waitFor(() => expect(screen.getAllByTestId('score-card')).toHaveLength(4)); + }); + + it('saves the evaluated grid so /geometry can reuse it as reference', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + const numbers = [3, 7, 19, 31, 42]; + const stars = [2, 9]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('evaluate-button')); + + await waitFor(() => expect(evaluatedGridRepository.getLast()).toEqual({ numbers, stars })); + }); +}); diff --git a/src/pages/evaluation/EvaluationPage.tsx b/src/pages/evaluation/EvaluationPage.tsx new file mode 100644 index 0000000..adc236f --- /dev/null +++ b/src/pages/evaluation/EvaluationPage.tsx @@ -0,0 +1,153 @@ +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import type { Draw } from '../../domain/draw/Draw.ts'; +import { generateVariations } from '../../application/generateVariations.ts'; +import { VARIATION_DESCRIPTIONS, VARIATION_LABELS } from '../../application/variationLabels.ts'; +import { drawRepository } from '../../application/drawRepository.ts'; +import { evaluatedGridRepository } from '../../application/evaluatedGridRepository.ts'; +import type { Grid } from '../../domain/grid/Grid.ts'; +import type { EvaluationScores, ReadingMatrixLabel } from '../../domain/scoring/evaluateGrid.ts'; +import { classifyReading, evaluateGrid } from '../../domain/scoring/evaluateGrid.ts'; +import { findExactMatch, findNumbersOnlyMatches } from '../../domain/draw/findExactMatch.ts'; +import GridInputForm from '../../components/gridInput/GridInputForm.tsx'; +import ScoreCard from './ScoreCard.tsx'; +import styles from './EvaluationPage.module.scss'; + +const EMPTY_HISTORY: Draw[] = []; + +const READING_LABELS: Record = { + classique: 'Classique', + interessante: 'Intéressante', + atypique: 'Atypique', + 'tres-atypique': 'Très atypique', +}; + +const EvaluationPage = () => { + const [grid, setGrid] = useState(() => evaluatedGridRepository.getLast()); + + const historyQuery = useQuery({ + queryKey: ['draws', 'all'], + queryFn: () => drawRepository.getAll(), + }); + const history = historyQuery.data ?? EMPTY_HISTORY; + + const handleGridSubmit = (submittedGrid: Grid) => { + setGrid(submittedGrid); + evaluatedGridRepository.save(submittedGrid); + }; + + const scores = useMemo( + () => (grid && history.length > 0 ? evaluateGrid(grid, history) : null), + [grid, history], + ); + const variations = useMemo( + () => + grid && history.length > 0 + ? generateVariations(grid, history).map((variation) => ({ + ...variation, + scores: evaluateGrid(variation.grid, history), + })) + : [], + [grid, history], + ); + const reading = scores ? classifyReading(scores) : null; + + const exactMatch = useMemo( + () => (grid && history.length > 0 ? findExactMatch(grid, history) : null), + [grid, history], + ); + const numbersOnlyMatches = useMemo( + () => (grid && history.length > 0 && !exactMatch ? findNumbersOnlyMatches(grid, history) : []), + [grid, history, exactMatch], + ); + const earliestDrawDate = useMemo( + () => + history.length === 0 + ? null + : history.reduce((earliest, draw) => (draw.date < earliest ? draw.date : earliest), history[0].date), + [history], + ); + + return ( +
+

Évaluation d'une grille

+ + + + {historyQuery.isLoading &&

Chargement de l'historique...

} + + {scores && reading && ( +
+

Lecture : {READING_LABELS[reading]}

+ {earliestDrawDate && ( +

+ {exactMatch + ? `Cette grille est déjà sortie le ${exactMatch.date} — la retirer ne change rien à ses chances de sortir à nouveau.` + : `Cette grille n'est jamais sortie dans l'historique disponible (depuis ${earliestDrawDate}) — comme la grande majorité des combinaisons possibles.`} +

+ )} + {numbersOnlyMatches.length > 0 && ( +

+ Les 5 numéros de cette grille sont déjà sortis, avec des étoiles différentes :{' '} + {numbersOnlyMatches.map((draw) => draw.date).join(', ')}. +

+ )} +
+ + + + +
+
+ )} + + {variations.length > 0 && ( +
+

Variations

+

+ Ces variations ne sont pas présentées comme plus susceptibles d'être tirées : toutes les + grilles valides ont la même probabilité théorique. +

+
    + {variations.map((variation) => ( +
  • +
    + {VARIATION_LABELS[variation.kind]} + + {variation.grid.numbers.join(' · ')} — étoiles {variation.grid.stars.join(' · ')} + +
    +

    {VARIATION_DESCRIPTIONS[variation.kind]}

    +
    + + + Structure historique : {variation.scores.structure.value} + + + + Originalité estimée : {variation.scores.originality.value} + + + + Temporalité : {variation.scores.temporal.value} + + + + Confiance : {variation.scores.confidence.value} + +
    +
  • + ))} +
+
+ )} +
+ ); +}; + +export default EvaluationPage; diff --git a/src/pages/evaluation/ScoreCard.module.scss b/src/pages/evaluation/ScoreCard.module.scss new file mode 100644 index 0000000..3f0bc82 --- /dev/null +++ b/src/pages/evaluation/ScoreCard.module.scss @@ -0,0 +1,77 @@ +.card { + --card-accent: var(--accent-primary); + + border: 1px solid var(--border); + border-top: 3px solid var(--card-accent); + border-radius: var(--radius-md); + background: var(--surface); + padding: var(--space-4) var(--space-4) var(--space-3); + text-align: left; + box-shadow: var(--shadow-sm); +} + +.accentStructure { + --card-accent: var(--accent-structure); +} + +.accentOriginality { + --card-accent: var(--accent-originality); +} + +.accentTemporal { + --card-accent: var(--accent-temporal); +} + +.accentConfidence { + --card-accent: var(--accent-confidence); +} + +.card h3 { + margin: 0 0 var(--space-2); + font-size: var(--text-sm); + color: var(--text-muted); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.value { + font-family: var(--mono); + font-size: var(--text-2xl); + font-weight: 700; + color: var(--card-accent); + margin: 0 0 var(--space-3); +} + +.valueMax { + font-size: var(--text-sm); + color: var(--text-muted); + font-weight: 500; + margin-left: var(--space-1); +} + +.factors { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); + font-size: var(--text-xs); + color: var(--text-muted); + + li { + display: flex; + justify-content: space-between; + gap: var(--space-2); + } +} + +.description { + margin: var(--space-3) 0 0; + padding-top: var(--space-3); + border-top: 1px solid var(--border); + font-size: var(--text-xs); + line-height: 1.5; + color: var(--text-muted); +} diff --git a/src/pages/evaluation/ScoreCard.tsx b/src/pages/evaluation/ScoreCard.tsx new file mode 100644 index 0000000..ac0a15a --- /dev/null +++ b/src/pages/evaluation/ScoreCard.tsx @@ -0,0 +1,74 @@ +import type { Score } from '../../domain/scoring/evaluateGrid.ts'; +import styles from './ScoreCard.module.scss'; + +export type ScoreAccent = 'structure' | 'originality' | 'temporal' | 'confidence'; + +interface ScoreCardProps { + title: string; + score: Score; + accent: ScoreAccent; +} + +const ACCENT_CLASSES: Record = { + structure: styles.accentStructure, + originality: styles.accentOriginality, + temporal: styles.accentTemporal, + confidence: styles.accentConfidence, +}; + +const ACCENT_DESCRIPTIONS: Record = { + structure: + "Indique si la forme de cette grille (somme, écarts, répartition par dizaine) ressemble aux tirages historiques les plus courants.", + originality: + "Évalue si cette grille correspond à des choix populaires chez les joueurs (dates, numéros consécutifs...) - sans effet sur vos chances réelles de gagner.", + temporal: "Compare cette grille aux tirages récents, sur des périodes allant d'un an à tout l'historique.", + confidence: + 'Indique la fiabilité du diagnostic ci-dessus : plus les tirages proches de cette grille sont cohérents entre eux, plus la confiance est élevée.', +}; + +const FACTOR_LABELS: Record = { + meanNeighborDistance: 'Distance moyenne aux tirages proches', + sumPercentile: 'Percentile de la somme', + amplitudePercentile: "Percentile de l'amplitude", + decadeSignatureMatchRate: 'Tirages avec la même répartition par dizaine', + parityMatchRate: 'Tirages avec la même répartition pair/impair', + consecutivePairsCount: 'Numéros consécutifs', + sameUnitsPairsCount: 'Numéros finissant par le même chiffre', + multiplesOfFiveCount: 'Multiples de 5', + aboveThirtyOneCount: 'Numéros supérieurs à 31', + window_1: 'Sur 1 an', + window_3: 'Sur 3 ans', + window_6: 'Sur 6 ans', + window_12: 'Sur 12 ans', + window_25: 'Sur 25 ans', + window_50: 'Sur 50 ans', + window_all: "Sur tout l'historique", + sampleSize: 'Nombre de tirages analysés', + neighborMeanDistance: 'Distance moyenne aux tirages proches', + neighborDistanceStdDev: 'Écart-type de cette distance', +}; + +const factorLabel = (rawLabel: string): string => FACTOR_LABELS[rawLabel] ?? rawLabel; + +const ScoreCard = ({ title, score, accent }: ScoreCardProps) => { + return ( +
+

{title}

+

+ {score.value} + /100 +

+
    + {score.factors.map((factor) => ( +
  • + {factorLabel(factor.label)} + {Math.round(factor.value * 100) / 100} +
  • + ))} +
+

{ACCENT_DESCRIPTIONS[accent]}

+
+ ); +}; + +export default ScoreCard; diff --git a/src/pages/geometry/GeometryPage.module.scss b/src/pages/geometry/GeometryPage.module.scss new file mode 100644 index 0000000..2745860 --- /dev/null +++ b/src/pages/geometry/GeometryPage.module.scss @@ -0,0 +1,167 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-6); + text-align: left; +} + +.intro { + margin: 0; + color: var(--text-muted); + font-size: var(--text-sm); + line-height: 1.6; + max-width: 62ch; +} + +.sourceSelector { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.sourceButton, +.sourceButtonActive { + background: var(--surface); + color: var(--text); + border: 1px solid var(--border-strong); + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.sourceButtonActive { + background: var(--accent-primary); + color: #0a0c11; + border-color: var(--accent-primary); +} + +.sourceHint { + margin: 0; + color: var(--text-muted); + font-size: var(--text-xs); +} + +.emptyCustomHint { + margin: 0; + color: var(--text-muted); + font-size: var(--text-sm); + font-style: italic; +} + +.section { + display: flex; + flex-direction: column; + gap: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface); + padding: var(--space-5); +} + +.description { + margin: 0; + color: var(--text-muted); + font-size: var(--text-sm); + line-height: 1.5; +} + +.referenceBanner { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface); + padding: var(--space-4) var(--space-5); +} + +.referenceMeta { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.referenceLabel { + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); +} + +.referenceBubbles { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.chainGroup { + display: flex; + flex-direction: column; + gap: var(--space-2); + padding: var(--space-3) 0; + + &:not(:last-child) { + border-bottom: 1px solid var(--border); + } +} + +.chainLabel { + font-size: var(--text-sm); + font-weight: 600; + color: var(--text); +} + +.chainNote { + margin: 0; + color: var(--text-muted); + font-size: var(--text-xs); +} + +.neighborList { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.neighborRow { + display: grid; + grid-template-columns: 6rem 1fr 8rem; + align-items: center; + gap: var(--space-3); + padding: var(--space-2) 0; + + &:not(:last-child) { + border-bottom: 1px solid var(--border); + } +} + +.neighborDate { + font-size: var(--text-xs); + color: var(--text-muted); + font-family: var(--mono); +} + +.neighborBubbles { + display: flex; + gap: var(--space-1); +} + +.neighborProximity { + height: 0.5rem; + border-radius: var(--radius-sm); + background: var(--border); + overflow: hidden; +} + +.neighborProximityFill { + display: block; + height: 100%; + background: var(--accent-reference); + border-radius: var(--radius-sm); +} diff --git a/src/pages/geometry/GeometryPage.test.tsx b/src/pages/geometry/GeometryPage.test.tsx new file mode 100644 index 0000000..3b1f6d9 --- /dev/null +++ b/src/pages/geometry/GeometryPage.test.tsx @@ -0,0 +1,79 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderWithProviders } from '../../shared/renderWithProviders.tsx'; +import { REAL_CSV_TEXT } from '../../shared/testCsvFixture.ts'; +import { evaluatedGridRepository } from '../../application/evaluatedGridRepository.ts'; +import GeometryPage from './GeometryPage.tsx'; + +describe('GeometryPage', () => { + beforeEach(() => { + localStorage.clear(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + localStorage.clear(); + }); + + it('renders without crashing and shows the gap map and nearest neighbors once loaded', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + expect(screen.getByRole('heading', { name: 'Géométrie', level: 1 })).toBeInTheDocument(); + await waitFor(() => expect(screen.getAllByTestId('neighbor-row')).toHaveLength(10)); + expect(screen.getByTestId('gap-map')).toBeInTheDocument(); + }); + + it('defaults to the latest draw and disables the "evaluated" source when none was saved', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + await waitFor(() => expect(screen.getByTestId('reference-grid-banner')).toBeInTheDocument()); + expect(screen.getByTestId('reference-source-latest')).toHaveAttribute('aria-checked', 'true'); + expect(screen.getByTestId('reference-source-evaluated')).toBeDisabled(); + }); + + it('uses the grid evaluated on /evaluation as the reference when one was saved', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + evaluatedGridRepository.save({ numbers: [3, 7, 19, 31, 42], stars: [2, 9] }); + + renderWithProviders(); + + await waitFor(() => + expect(screen.getByTestId('reference-source-evaluated')).toHaveAttribute('aria-checked', 'true'), + ); + const banner = screen.getByTestId('reference-grid-banner'); + for (const value of [3, 7, 19, 31, 42, 2, 9]) { + expect(banner).toHaveTextContent(String(value)); + } + }); + + it('lets the user type a grid directly on the page to analyze it', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + await waitFor(() => expect(screen.getByTestId('reference-grid-banner')).toBeInTheDocument()); + + await user.click(screen.getByTestId('reference-source-custom')); + expect(screen.getByTestId('custom-grid-empty')).toBeInTheDocument(); + + const numbers = [4, 8, 15, 23, 44]; + const stars = [1, 5]; + for (const [index, value] of numbers.entries()) { + await user.type(screen.getByTestId(`number-input-${index}`), String(value)); + } + for (const [index, value] of stars.entries()) { + await user.type(screen.getByTestId(`star-input-${index}`), String(value)); + } + await user.click(screen.getByTestId('custom-grid-submit')); + + const banner = screen.getByTestId('reference-grid-banner'); + for (const value of [...numbers, ...stars]) { + expect(banner).toHaveTextContent(String(value)); + } + }); +}); diff --git a/src/pages/geometry/GeometryPage.tsx b/src/pages/geometry/GeometryPage.tsx new file mode 100644 index 0000000..9720dc1 --- /dev/null +++ b/src/pages/geometry/GeometryPage.tsx @@ -0,0 +1,365 @@ +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { + Bar, + BarChart, + CartesianGrid, + ResponsiveContainer, + Scatter, + ScatterChart, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import Bubble from '../../components/bubble/Bubble.tsx'; +import NumberChain from '../../components/bubble/NumberChain.tsx'; +import ChartLegend from '../../components/chartLegend/ChartLegend.tsx'; +import GridInputForm from '../../components/gridInput/GridInputForm.tsx'; +import { drawRepository } from '../../application/drawRepository.ts'; +import { evaluatedGridRepository } from '../../application/evaluatedGridRepository.ts'; +import { generateVariations } from '../../application/generateVariations.ts'; +import { VARIATION_LABELS } from '../../application/variationLabels.ts'; +import type { Draw } from '../../domain/draw/Draw.ts'; +import type { Grid } from '../../domain/grid/Grid.ts'; +import { extractFeatures } from '../../domain/features/FeatureExtractor.ts'; +import { buildGeometryDescriptor } from '../../domain/geometry/GeometryDescriptor.ts'; +import { findNearestNeighbors } from '../../domain/geometry/nearestNeighbors.ts'; +import styles from './GeometryPage.module.scss'; + +const DECADE_LABELS = ['1-10', '11-20', '21-30', '31-40', '41-50']; +const NEIGHBOR_COUNT = 10; +const EMPTY_DRAWS: Draw[] = []; + +type ReferenceSource = 'latest' | 'evaluated' | 'custom'; + +const SOURCE_LABELS: Record = { + latest: 'Dernier tirage', + evaluated: 'Grille évaluée', + custom: 'Saisie libre', +}; + +const SOURCE_HINTS: Record = { + latest: 'Le dernier tirage officiel enregistré, pris comme exemple.', + evaluated: "La grille que vous avez évaluée sur /evaluation.", + custom: "Entrez n'importe quelle grille pour la situer dans l'historique.", +}; + +interface HighlightDotProps { + cx?: number; + cy?: number; +} + +const HighlightDot = ({ cx, cy }: HighlightDotProps) => ( + +); + +interface DecadeBarProps { + x?: number; + y?: number; + width?: number; + height?: number; + fill?: string; + payload?: { hasReference: boolean }; +} + +const DecadeBar = ({ x = 0, y = 0, width = 0, height = 0, fill, payload }: DecadeBarProps) => ( + + + {payload?.hasReference && ( + + )} + +); + +const GeometryPage = () => { + const drawsQuery = useQuery({ + queryKey: ['draws', 'all'], + queryFn: () => drawRepository.getAll(), + }); + const draws = drawsQuery.data ?? EMPTY_DRAWS; + + const evaluatedGrid = useMemo(() => evaluatedGridRepository.getLast(), []); + + const [source, setSource] = useState(evaluatedGrid ? 'evaluated' : 'latest'); + const [customGrid, setCustomGrid] = useState(null); + + const entries = useMemo( + () => + [...draws] + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .map((draw) => ({ + draw, + descriptor: buildGeometryDescriptor({ numbers: draw.numbers, stars: draw.stars }), + stdDev: extractFeatures({ numbers: draw.numbers, stars: draw.stars }).values.stdDev, + })), + [draws], + ); + + const latestEntry = entries[0]; + const latestGrid: Grid | null = latestEntry ? { numbers: latestEntry.draw.numbers, stars: latestEntry.draw.stars } : null; + + const referenceGrid: Grid | null = + source === 'latest' ? latestGrid : source === 'evaluated' ? evaluatedGrid : customGrid; + + const referenceDescriptor = useMemo( + () => (referenceGrid ? buildGeometryDescriptor(referenceGrid) : null), + [referenceGrid], + ); + const referenceStdDev = useMemo( + () => (referenceGrid ? extractFeatures(referenceGrid).values.stdDev : null), + [referenceGrid], + ); + + const sumAmplitudeData = entries.map((entry) => ({ sum: entry.descriptor.sum, range: entry.descriptor.range })); + const sumStdDevData = entries.map((entry) => ({ sum: entry.descriptor.sum, stdDev: entry.stdDev })); + + const decadeHistogram = DECADE_LABELS.map((label, index) => ({ + decade: label, + count: entries.reduce((acc, entry) => acc + entry.descriptor.decadeBuckets[index], 0), + hasReference: Boolean(referenceDescriptor && referenceDescriptor.decadeBuckets[index] > 0), + })); + + const referenceSumAmplitude = referenceDescriptor + ? [{ sum: referenceDescriptor.sum, range: referenceDescriptor.range }] + : []; + const referenceSumStdDev = + referenceDescriptor && referenceStdDev != null ? [{ sum: referenceDescriptor.sum, stdDev: referenceStdDev }] : []; + + // Excludes the reference draw from its own neighbor candidates only when it + // actually comes from the historical list ('latest') - an evaluated/custom + // grid isn't part of `entries`, so nothing needs excluding for it. + const neighborCandidates = source === 'latest' ? entries.slice(1) : entries; + const neighbors = referenceDescriptor + ? findNearestNeighbors( + referenceDescriptor, + neighborCandidates.map((entry) => ({ item: entry.draw, descriptor: entry.descriptor })), + NEIGHBOR_COUNT, + ) + : []; + const maxNeighborDistance = neighbors.length > 0 ? Math.max(...neighbors.map((n) => n.distance)) : 0; + + const referenceVariation = useMemo(() => { + if (!referenceGrid || draws.length === 0) return null; + const [variation] = generateVariations(referenceGrid, draws); + return variation ?? null; + }, [referenceGrid, draws]); + + return ( +
+

Géométrie

+

+ Cette page situe une grille - le dernier tirage, votre grille évaluée sur /evaluation, ou une + saisie libre - au sein de l'historique complet des tirages, à travers plusieurs mesures + géométriques (somme, amplitude, écart-type, répartition par dizaines, écarts entre numéros). + Elle ne prédit rien : toutes les grilles ont la même probabilité théorique de sortir - elle + sert seulement à comparer une forme à des formes déjà observées. +

+ + {drawsQuery.isLoading &&

Chargement...

} + +
+ {(Object.keys(SOURCE_LABELS) as ReferenceSource[]).map((candidate) => ( + + ))} +
+

{SOURCE_HINTS[source]}

+ + {source === 'custom' && ( + + )} + + {referenceGrid ? ( +
+
+ Grille de référence + {source === 'latest' ? latestEntry?.draw.date : SOURCE_LABELS[source]} +
+
+ {referenceGrid.numbers.map((n) => ( + + ))} + {referenceGrid.stars.map((s) => ( + + ))} +
+
+ ) : ( + source === 'custom' && ( +

+ Saisissez une grille ci-dessus pour l'analyser. +

+ ) + )} + + {entries.length > 0 && ( + <> +
+

Somme × Amplitude

+

+ Chaque point est un tirage historique : la somme des 5 numéros (axe horizontal) et l'amplitude, l'écart + entre le plus petit et le plus grand numéro (axe vertical). Un point isolé, loin du nuage principal, + signifie une somme ou une amplitude inhabituelle par rapport à l'historique - ça ne présage en rien de + la suite. +

+ + + + + + + + + + + +
+ +
+

Somme × Écart-type

+

+ Même principe, avec l'écart-type (axe vertical) : il mesure si les 5 numéros sont plutôt regroupés ou + dispersés autour de leur moyenne. Là encore, un point excentré indique juste un profil rare dans + l'historique, pas un signal prédictif. +

+ + + + + + + + + + + +
+ +
+

Histogramme des signatures de dizaines

+

+ Sur tout l'historique, combien de numéros sont tombés dans chacune des cinq tranches de dizaines + (1-10, 11-20, 21-30, 31-40, 41-50). Le repère au-dessus d'une barre indique les dizaines où tombent les + numéros de la grille de référence - une barre plus haute que les autres reflète simplement une + fréquence historique, pas une dizaine "due". +

+ + + + + + + + + + +
+ + {referenceGrid && referenceDescriptor && ( + <> +
+

Carte des écarts

+

+ Les 5 numéros d'une grille, dans l'ordre croissant, reliés par l'écart (la distance) qui les + sépare - une façon de visualiser sa "forme" plutôt que sa seule liste de numéros. +

+ +
+ + Grille de référence + {source === 'latest' && latestEntry ? ` (${latestEntry.draw.date})` : ''} + + +
+ + {referenceVariation && ( +
+ + Variation proposée : {VARIATION_LABELS[referenceVariation.kind]} + + +

+ Comparez les deux formes : mêmes principes de calcul, écarts différents. Cette variation n'est + pas présentée comme plus susceptible d'être tirée. +

+
+ )} +
+ +
+

Voisins historiques (proximité géométrique)

+

+ Ce ne sont pas les derniers tirages : ce sont, parmi tout l'historique, les {NEIGHBOR_COUNT} dont la + forme géométrique (somme, écarts, répartition) ressemble le plus à la grille de référence. Une + ressemblance de forme ne signifie pas une probabilité de sortie plus élevée pour l'un ou l'autre - + la barre ci-dessous représente juste cette proximité relative, du plus proche au moins proche. +

+
    + {neighbors.map((neighbor) => { + const proximity = maxNeighborDistance > 0 ? 1 - neighbor.distance / maxNeighborDistance : 1; + return ( +
  • + {neighbor.item.date} +
    + {neighbor.item.numbers.map((n) => ( + + ))} +
    +
    + +
    +
  • + ); + })} +
+
+ + )} + + )} +
+ ); +}; + +export default GeometryPage; diff --git a/src/pages/laboratory/ExperimentResults.tsx b/src/pages/laboratory/ExperimentResults.tsx new file mode 100644 index 0000000..f6b596d --- /dev/null +++ b/src/pages/laboratory/ExperimentResults.tsx @@ -0,0 +1,143 @@ +import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +import type { Experiment } from '../../application/runExperiment.ts'; +import { buildHistogram } from './buildHistogram.ts'; +import styles from './LaboratoryPage.module.scss'; + +interface ExperimentResultsProps { + experiment: Experiment; + onExport: (experiment: Experiment) => void; +} + +const windowLabel = (window: Experiment['windows'][number]): string => (window === 'all' ? 'Tout' : `${window} an(s)`); + +const ExperimentResults = ({ experiment, onExport }: ExperimentResultsProps) => { + const histogramData = buildHistogram(experiment.monteCarlo.distribution, 10); + const isNoAdvantageDetected = Math.abs(experiment.monteCarlo.strategyPercentile - 50) < 10; + + return ( +
+

Résultats — {experiment.strategy.name}

+ + {experiment.overfitting.isLikelyOverfit && ( +

+ ⚠ Risque de surapprentissage : {experiment.overfitting.reason} +

+ )} + +
+

+ Test — numéros trouvés en moyenne :{' '} + {experiment.results.metrics.meanMatchedNumbers.toFixed(2)} / 5, étoiles :{' '} + {experiment.results.metrics.meanMatchedStars.toFixed(2)} / 2 +

+

+ Meilleur rang obtenu sur la période de test :{' '} + {experiment.results.metrics.bestPrizeRank ?? 'aucun'} +

+

+ Train : {experiment.overfitting.trainMeanMatchedNumbers.toFixed(2)} — Validation :{' '} + {experiment.overfitting.validationMeanMatchedNumbers.toFixed(2)} — Test :{' '} + {experiment.overfitting.testMeanMatchedNumbers.toFixed(2)} +

+
+ +
+

Comparaison aux références

+
+ + + + + + + + + + {experiment.results.baselineComparisons.map((baseline) => ( + + + + + + ))} + +
RéférenceNuméros moy.Étoiles moy.
{baseline.baselineName}{baseline.meanMatchedNumbers.toFixed(2)}{baseline.meanMatchedStars.toFixed(2)}
+
+
+ +
+

Simulation Monte Carlo

+

+ Percentile de la stratégie testée : {experiment.monteCarlo.strategyPercentile.toFixed(1)}e sur{' '} + {experiment.monteCarlo.sampleCount} stratégies aléatoires équivalentes. +

+ {isNoAdvantageDetected &&

Conclusion : aucun avantage détectable par rapport au hasard.

} + + + + + + + + + +
+ +
+

Comparaison par fenêtre temporelle

+
+ + + + + + + + + {experiment.windowComparisons.map((comparison) => ( + + + + + ))} + +
FenêtreNuméros moy.
{windowLabel(comparison.window)}{comparison.metrics.meanMatchedNumbers.toFixed(2)}
+
+
+ +
+

Grilles générées (période de test)

+
+ + + + + + + + + + + {experiment.results.generatedGrids.map((generated) => ( + + + + + + + ))} + +
DateGrille proposéeÉtoilesNuméros trouvés
{generated.date}{generated.grid.numbers.join(' · ')}{generated.grid.stars.join(' · ')}{generated.score}
+
+
+ +

+ +

+
+ ); +}; + +export default ExperimentResults; diff --git a/src/pages/laboratory/LaboratoryPage.module.scss b/src/pages/laboratory/LaboratoryPage.module.scss new file mode 100644 index 0000000..63bc5b2 --- /dev/null +++ b/src/pages/laboratory/LaboratoryPage.module.scss @@ -0,0 +1,135 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-6); + text-align: left; +} + +.intro { + color: var(--text-muted); + font-size: var(--text-lg); + max-width: 42rem; +} + +.section { + display: flex; + flex-direction: column; + gap: var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface); + padding: var(--space-5); +} + +.ruleRow { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-2) 0; + border-bottom: 1px solid var(--border); + + &:last-child { + border-bottom: none; + } + + label { + display: flex; + align-items: center; + gap: var(--space-2); + color: var(--text); + font-size: var(--text-sm); + flex: 1; + } + + input[type='checkbox'] { + accent-color: var(--accent-primary); + width: 1rem; + height: 1rem; + } + + input[type='number'] { + width: 4.5rem; + font-family: var(--mono); + } +} + +.formRow { + display: flex; + flex-wrap: wrap; + gap: var(--space-5); + align-items: flex-end; +} + +.windowChecks { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + + label { + display: flex; + align-items: center; + gap: var(--space-1); + color: var(--text); + } + + input { + accent-color: var(--accent-primary); + } +} + +.tableWrapper { + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: auto; + max-height: 60vh; +} + +.table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + + th, + td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border); + text-align: left; + white-space: nowrap; + } + + thead th { + position: sticky; + top: 0; + background: var(--surface-raised); + color: var(--text-muted); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.04em; + font-weight: 600; + } + + tbody tr:hover { + background: var(--surface-raised); + } +} + +.warning { + border: 1px solid var(--danger); + background: var(--danger-surface); + color: var(--danger); + border-radius: var(--radius-md); + padding: var(--space-3) var(--space-4); +} + +.metrics { + display: flex; + flex-direction: column; + gap: var(--space-1); + color: var(--text-muted); + font-size: var(--text-sm); + + strong { + color: var(--text); + font-family: var(--mono); + } +} diff --git a/src/pages/laboratory/LaboratoryPage.test.tsx b/src/pages/laboratory/LaboratoryPage.test.tsx new file mode 100644 index 0000000..ca64e3c --- /dev/null +++ b/src/pages/laboratory/LaboratoryPage.test.tsx @@ -0,0 +1,34 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderWithProviders } from '../../shared/renderWithProviders.tsx'; +import { REAL_CSV_TEXT } from '../../shared/testCsvFixture.ts'; +import LaboratoryPage from './LaboratoryPage.tsx'; + +describe('LaboratoryPage', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('renders without crashing', () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + + renderWithProviders(); + + expect(screen.getByRole('heading', { name: 'Laboratoire de stratégies' })).toBeInTheDocument(); + }); + + it('runs a backtest and shows results with baselines and Monte Carlo', async () => { + vi.stubGlobal('fetch', () => Promise.resolve(new Response(REAL_CSV_TEXT))); + const user = userEvent.setup(); + + renderWithProviders(); + + await user.click(screen.getByTestId('run-experiment-button')); + + await waitFor(() => expect(screen.getByTestId('experiment-results')).toBeInTheDocument(), { timeout: 10000 }); + expect(screen.getByTestId('baseline-comparison').querySelectorAll('tbody tr')).toHaveLength(5); + expect(screen.getByTestId('monte-carlo')).toBeInTheDocument(); + expect(screen.getAllByTestId('experiment-row')).toHaveLength(1); + }); +}); diff --git a/src/pages/laboratory/LaboratoryPage.tsx b/src/pages/laboratory/LaboratoryPage.tsx new file mode 100644 index 0000000..017f34f --- /dev/null +++ b/src/pages/laboratory/LaboratoryPage.tsx @@ -0,0 +1,277 @@ +import { useState } from 'react'; +import type { ChangeEvent } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { drawRepository } from '../../application/drawRepository.ts'; +import type { Experiment } from '../../application/runExperiment.ts'; +import { runExperiment } from '../../application/runExperiment.ts'; +import type { Draw } from '../../domain/draw/Draw.ts'; +import { TEMPORAL_WINDOWS } from '../../domain/scoring/TemporalWindow.ts'; +import type { TemporalWindow } from '../../domain/scoring/TemporalWindow.ts'; +import type { Strategy } from '../../domain/strategy/Strategy.ts'; +import ExperimentResults from './ExperimentResults.tsx'; +import { + DEFAULT_RULES_STATE, + RULE_KINDS, + RULE_LABELS, + SCORING_KINDS, + buildRulesFromState, +} from './strategyFormRules.ts'; +import type { RulesState } from './strategyFormRules.ts'; +import styles from './LaboratoryPage.module.scss'; + +const EMPTY_DRAWS: Draw[] = []; +const DEFAULT_MONTE_CARLO_SAMPLES = 200; + +const windowLabel = (window: TemporalWindow): string => (window === 'all' ? 'Tout' : `${window} an(s)`); + +const downloadExperiment = (experiment: Experiment) => { + const blob = new Blob([JSON.stringify(experiment, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `experiment-${experiment.id}.json`; + link.click(); + URL.revokeObjectURL(url); +}; + +const LaboratoryPage = () => { + const [strategyName, setStrategyName] = useState('Ma stratégie'); + const [seed, setSeed] = useState(42); + const [rulesState, setRulesState] = useState(DEFAULT_RULES_STATE); + const [trainPercent, setTrainPercent] = useState(70); + const [validationPercent, setValidationPercent] = useState(15); + const [selectedWindows, setSelectedWindows] = useState(['all']); + const [monteCarloSampleCount, setMonteCarloSampleCount] = useState(DEFAULT_MONTE_CARLO_SAMPLES); + const [experiments, setExperiments] = useState([]); + const [selectedExperimentId, setSelectedExperimentId] = useState(null); + + const historyQuery = useQuery({ queryKey: ['draws', 'all'], queryFn: () => drawRepository.getAll() }); + const draws = historyQuery.data ?? EMPTY_DRAWS; + const testPercent = 100 - trainPercent - validationPercent; + + const updateRule = (kind: (typeof RULE_KINDS)[number], patch: Partial) => { + setRulesState((previous) => ({ ...previous, [kind]: { ...previous[kind], ...patch } })); + }; + + const toggleWindow = (window: TemporalWindow) => { + setSelectedWindows((previous) => + previous.includes(window) ? previous.filter((w) => w !== window) : [...previous, window], + ); + }; + + const handleRunExperiment = () => { + const strategy: Strategy = { + id: crypto.randomUUID(), + name: strategyName, + rules: buildRulesFromState(rulesState), + seed, + }; + + const experiment = runExperiment({ + strategy, + draws, + trainRatio: trainPercent / 100, + validationRatio: validationPercent / 100, + windows: selectedWindows, + monteCarloSampleCount, + }); + + setExperiments((previous) => [experiment, ...previous]); + setSelectedExperimentId(experiment.id); + }; + + const selectedExperiment = experiments.find((experiment) => experiment.id === selectedExperimentId) ?? null; + + return ( +
+

Laboratoire de stratégies

+

+ Cette hypothèse fait-elle mieux qu'une stratégie de référence lorsqu'on la teste correctement ? +

+ +
+

Construire une stratégie

+
+ + +
+ +
+ Règles + {RULE_KINDS.map((kind) => { + const config = rulesState[kind]; + return ( +
+ + {config.enabled && SCORING_KINDS.includes(kind) && ( + ) => updateRule(kind, { weight: Number(event.target.value) })} + aria-label={`Poids - ${RULE_LABELS[kind]}`} + data-testid={`rule-weight-${kind}`} + /> + )} + {config.enabled && kind === 'sum-range' && ( + <> + ) => updateRule(kind, { min: Number(event.target.value) })} + aria-label="Somme minimum" + /> + ) => updateRule(kind, { max: Number(event.target.value) })} + aria-label="Somme maximum" + /> + + )} + {config.enabled && kind === 'parity-target' && ( + ) => updateRule(kind, { oddCount: Number(event.target.value) })} + aria-label="Nombre de numéros impairs souhaité" + /> + )} +
+ ); + })} +
+
+ +
+

Configuration de l'expérience

+
+ + +

Test : {testPercent}%

+
+ +
+ Fenêtres temporelles (aucune n'a de statut privilégié) +
+ {TEMPORAL_WINDOWS.map((window) => ( + + ))} +
+
+ + + +

+ +

+
+ + {experiments.length > 0 && ( +
+

Expériences de cette session ({experiments.length})

+
+ + + + + + + + + + + {experiments.map((experiment) => ( + + + + + + + + ))} + +
StratégieSeedNuméros moy. (test)Percentile Monte Carlo +
{experiment.strategy.name}{experiment.seed}{experiment.results.metrics.meanMatchedNumbers.toFixed(2)}{experiment.monteCarlo.strategyPercentile.toFixed(0)}e + +
+
+
+ )} + + {selectedExperiment && } +
+ ); +}; + +export default LaboratoryPage; diff --git a/src/pages/laboratory/buildHistogram.test.ts b/src/pages/laboratory/buildHistogram.test.ts new file mode 100644 index 0000000..6d4d4c7 --- /dev/null +++ b/src/pages/laboratory/buildHistogram.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { buildHistogram } from './buildHistogram.ts'; + +describe('buildHistogram', () => { + it('returns an empty array for no values', () => { + expect(buildHistogram([], 10)).toEqual([]); + }); + + it('distributes every value into exactly one of the requested bins', () => { + const values = [0, 1, 2, 3, 4, 5]; + + const bins = buildHistogram(values, 5); + + expect(bins).toHaveLength(5); + expect(bins.reduce((acc, bin) => acc + bin.count, 0)).toBe(values.length); + }); + + it('puts identical values into the same bin', () => { + const bins = buildHistogram([2, 2, 2, 2], 4); + + expect(bins.reduce((acc, bin) => acc + bin.count, 0)).toBe(4); + expect(bins.filter((bin) => bin.count > 0)).toHaveLength(1); + }); +}); diff --git a/src/pages/laboratory/buildHistogram.ts b/src/pages/laboratory/buildHistogram.ts new file mode 100644 index 0000000..8fba0c4 --- /dev/null +++ b/src/pages/laboratory/buildHistogram.ts @@ -0,0 +1,24 @@ +export interface HistogramBin { + label: string; + count: number; +} + +export const buildHistogram = (values: number[], binCount: number): HistogramBin[] => { + if (values.length === 0) return []; + + const min = Math.min(...values); + const max = Math.max(...values); + const range = max - min || 1; + const binSize = range / binCount; + const bins: HistogramBin[] = Array.from({ length: binCount }, (_, i) => ({ + label: (min + i * binSize).toFixed(2), + count: 0, + })); + + for (const value of values) { + const index = Math.min(Math.floor((value - min) / binSize), binCount - 1); + bins[index].count += 1; + } + + return bins; +}; diff --git a/src/pages/laboratory/strategyFormRules.test.ts b/src/pages/laboratory/strategyFormRules.test.ts new file mode 100644 index 0000000..2e63991 --- /dev/null +++ b/src/pages/laboratory/strategyFormRules.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; +import { DEFAULT_RULES_STATE, buildRulesFromState } from './strategyFormRules.ts'; + +describe('buildRulesFromState', () => { + it('only includes enabled rules', () => { + const rules = buildRulesFromState(DEFAULT_RULES_STATE); + + expect(rules.map((r) => r.kind).sort()).toEqual(['number-frequency', 'star-frequency']); + }); + + it('carries params for sum-range and parity-target, weight for scoring kinds', () => { + const state = { + ...DEFAULT_RULES_STATE, + 'sum-range': { enabled: true, weight: 1, min: 90, max: 130 }, + 'parity-target': { enabled: true, weight: 1, oddCount: 4 }, + 'above-31': { enabled: true, weight: -2 }, + }; + + const rules = buildRulesFromState(state); + + expect(rules).toContainEqual({ kind: 'sum-range', params: { min: 90, max: 130 } }); + expect(rules).toContainEqual({ kind: 'parity-target', params: { oddCount: 4 } }); + expect(rules).toContainEqual({ kind: 'above-31', weight: -2 }); + }); + + it('carries no params for decade-spread', () => { + const state = { ...DEFAULT_RULES_STATE, 'decade-spread': { enabled: true, weight: 1 } }; + + const rules = buildRulesFromState(state); + + expect(rules).toContainEqual({ kind: 'decade-spread' }); + }); +}); diff --git a/src/pages/laboratory/strategyFormRules.ts b/src/pages/laboratory/strategyFormRules.ts new file mode 100644 index 0000000..975827d --- /dev/null +++ b/src/pages/laboratory/strategyFormRules.ts @@ -0,0 +1,61 @@ +import type { StrategyRule, StrategyRuleKind } from '../../domain/strategy/Strategy.ts'; + +export interface RuleConfigState { + enabled: boolean; + weight: number; + min?: number; + max?: number; + oddCount?: number; +} + +export type RulesState = Record; + +export const RULE_KINDS: StrategyRuleKind[] = [ + 'number-frequency', + 'above-31', + 'repeat-from-previous', + 'recency', + 'decade-spread', + 'sum-range', + 'parity-target', + 'star-frequency', +]; + +export const SCORING_KINDS: StrategyRuleKind[] = [ + 'number-frequency', + 'above-31', + 'repeat-from-previous', + 'recency', + 'star-frequency', +]; + +export const RULE_LABELS: Record = { + 'number-frequency': 'Fréquence des numéros (poids négatif = rareté)', + 'above-31': 'Favoriser les numéros > 31', + 'repeat-from-previous': 'Répétition du tirage précédent', + recency: 'Proximité récente (poids négatif = numéros en retard)', + 'decade-spread': 'Répartition par dizaine (signature)', + 'sum-range': 'Plage de somme', + 'parity-target': 'Parité cible', + 'star-frequency': 'Fréquence des étoiles', +}; + +export const DEFAULT_RULES_STATE: RulesState = { + 'number-frequency': { enabled: true, weight: 1 }, + 'above-31': { enabled: false, weight: 1 }, + 'repeat-from-previous': { enabled: false, weight: 1 }, + recency: { enabled: false, weight: 1 }, + 'decade-spread': { enabled: false, weight: 1 }, + 'sum-range': { enabled: false, weight: 1, min: 100, max: 150 }, + 'parity-target': { enabled: false, weight: 1, oddCount: 3 }, + 'star-frequency': { enabled: true, weight: 1 }, +}; + +export const buildRulesFromState = (state: RulesState): StrategyRule[] => + RULE_KINDS.filter((kind) => state[kind].enabled).map((kind): StrategyRule => { + const config = state[kind]; + if (kind === 'sum-range') return { kind, params: { min: config.min, max: config.max } }; + if (kind === 'parity-target') return { kind, params: { oddCount: config.oddCount } }; + if (kind === 'decade-spread') return { kind }; + return { kind, weight: config.weight }; + }); diff --git a/src/pages/spike/SpikePage.tsx b/src/pages/spike/SpikePage.tsx new file mode 100644 index 0000000..d1a8b8c --- /dev/null +++ b/src/pages/spike/SpikePage.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; +import type { SpatialEmbedding } from '../../domain/geometry/SpatialEmbedding.ts'; +import { parseFdjCsv } from '../../infrastructure/csv/parseFdjCsv.ts'; +import { buildSpatialEmbeddings } from '../../application/buildSpatialEmbeddings.ts'; +import PointCloudScene from '../../spike/PointCloudScene.tsx'; + +const CSV_URL = `${import.meta.env.BASE_URL}results/euromillions_202002.csv`; + +const SpikePage = () => { + const [embeddings, setEmbeddings] = useState(null); + + useEffect(() => { + let cancelled = false; + + fetch(CSV_URL) + .then((response) => response.text()) + .then((csvText) => { + if (cancelled) return; + const draws = parseFdjCsv(csvText); + setEmbeddings(buildSpatialEmbeddings(draws)); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (!embeddings) return null; + + return ; +}; + +export default SpikePage; diff --git a/src/shared/renderWithProviders.tsx b/src/shared/renderWithProviders.tsx new file mode 100644 index 0000000..d801905 --- /dev/null +++ b/src/shared/renderWithProviders.tsx @@ -0,0 +1,8 @@ +import type { ReactElement } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render } from '@testing-library/react'; + +export const renderWithProviders = (element: ReactElement) => { + const queryClient = new QueryClient(); + return render({element}); +}; diff --git a/src/shared/testCsvFixture.ts b/src/shared/testCsvFixture.ts new file mode 100644 index 0000000..1c273b5 --- /dev/null +++ b/src/shared/testCsvFixture.ts @@ -0,0 +1,7 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +export const REAL_CSV_TEXT = readFileSync( + resolve(process.cwd(), 'public/results/euromillions_202002.csv'), + 'utf8', +); diff --git a/src/shared/testSetup.ts b/src/shared/testSetup.ts new file mode 100644 index 0000000..97650fd --- /dev/null +++ b/src/shared/testSetup.ts @@ -0,0 +1,7 @@ +import { afterEach } from 'vitest'; +import { cleanup } from '@testing-library/react'; +import '@testing-library/jest-dom/vitest'; + +afterEach(() => { + cleanup(); +}); diff --git a/src/spike/PointCloudScene.tsx b/src/spike/PointCloudScene.tsx new file mode 100644 index 0000000..21999ff --- /dev/null +++ b/src/spike/PointCloudScene.tsx @@ -0,0 +1,34 @@ +import { Canvas } from '@react-three/fiber'; +import { OrbitControls } from '@react-three/drei'; +import type { SpatialEmbedding } from '../domain/geometry/SpatialEmbedding.ts'; + +interface PointCloudSceneProps { + embeddings: SpatialEmbedding[]; +} + +const PointCloudScene = ({ embeddings }: PointCloudSceneProps) => { + const positions = new Float32Array(embeddings.length * 3); + embeddings.forEach((embedding, index) => { + positions[index * 3] = embedding.coordinates.x; + positions[index * 3 + 1] = embedding.coordinates.y; + positions[index * 3 + 2] = embedding.coordinates.z; + }); + + return ( +
+ + + + + + + + + + + +
+ ); +}; + +export default PointCloudScene; diff --git a/tsconfig.app.json b/tsconfig.app.json index 6830b6f..4ce531a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -4,7 +4,7 @@ "target": "es2023", "lib": ["ES2023", "DOM"], "module": "esnext", - "types": ["vite/client"], + "types": ["vite/client", "node"], "allowArbitraryExtensions": true, "skipLibCheck": true, diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..0f32683 --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..55102a5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,13 @@ -import { defineConfig } from 'vite' +import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ + base: process.env.VITE_BASE_PATH || '/', plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/shared/testSetup.ts'], + include: ['src/**/*.test.{ts,tsx}'], + }, })