SPB Git forge

spb/groupe-ka

Public

Groupe KA — site du holding + KA ID (compte unique & SSO des 7 plateformes). Next.js 16, SQLite, Google & Apple login.

81commits 1branches 0releases
89.2 MBsize
maindefault branch
22 days agolast push
TypeScript 70.4% HTML 18.4% JavaScript 4% Python 3.8% CSS 3.4%

/stats maître v2 : consolidation enrichie (KPI+sparklines, jauges, volume agrégé aligné sur dates communes, croissance comparée indice 100, volume empilé, calendriers agrégés, spotlights aux accents des marques, table détaillée, records écosystème) + kit kacharts/kapdf/SPEC v2 vendorisés + menu PdfButton 5 rapports branché sur /api/stats/report (alias du proxy ecosystem-report, 5 modes + from/to, tolérant)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 19, 2026) parent 3b9c688

1 changed file +33 −11

modified src/lib/ecostats.ts +33 −11
@@ -330,6 +330,13 @@ function commonDates(seriesList: SeriePoint[][]): string[] {
330 330 const byDateMap = (pts: SeriePoint[]): Map<string, number> =>
331 331 new Map(pts.map((p) => [p.t, p.v]));
332 332
333 +/* Seuls les points datés au jour (ISO) sont alignables entre plateformes —
334 + certaines publient des séries annuelles (ex. vrai-prix) qui ne se somment
335 + pas avec des séries quotidiennes. */
336 +const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
337 +const dailyPts = (pts: SeriePoint[]): SeriePoint[] =>
338 + pts.filter((p) => ISO_DAY.test(p.t));
339 +
333 340 export type SiteLite = Pick<Site, "id" | "wordmark" | "domain" | "accent">;
334 341
335 342 export type PlatformSummary = {
@@ -417,17 +424,32 @@ export function buildEcoPayload(
417 424 ages.length > 0 ? ages.reduce((s, v) => s + v, 0) / ages.length : null;
418 425 const v2count = ok.filter((p) => isV2(p.dash)).length;
419 426
420 − // contributeurs aux graphiques temporels : plateformes avec tendance ≥ 2 pts
427 + // contributeurs aux graphiques : plateformes avec une tendance ≥ 2 pts,
428 + // et sous-ensemble alignable = points datés au jour (ISO)
421 429 const withSpark = rankedOk
422 430 .map((p) => ({ p, spark: sparkOf(p.dash) }))
423 431 .filter((x) => x.spark.length >= 2);
432 + const withDaily = withSpark
433 + .map((x) => ({ p: x.p, spark: dailyPts(x.spark) }))
434 + .filter((x) => x.spark.length >= 2);
424 435
425 − // 1) série écosystème = somme des volumes sur les dates communes
436 + // 1) série écosystème = somme des volumes sur les dates communes.
437 + // Les fenêtres publiées varient (3 à 31 jours) : on écarte les fenêtres
438 + // les plus courtes tant que la fenêtre commune reste < 8 jours, pour
439 + // garder une courbe lisible sans jamais extrapoler.
426 440 let volume: EcoPayload["volume"] = null;
427 − if (withSpark.length > 0) {
428 − const dates = commonDates(withSpark.map((x) => x.spark));
429 − if (dates.length >= 2) {
430 − const maps = withSpark.map((x) => byDateMap(x.spark));
441 + {
442 + const pool = [...withDaily];
443 + let dates = commonDates(pool.map((x) => x.spark));
444 + while (pool.length > 2 && dates.length < 8) {
445 + let idx = 0;
446 + for (let i = 1; i < pool.length; i++)
447 + if (pool[i].spark.length < pool[idx].spark.length) idx = i;
448 + pool.splice(idx, 1);
449 + dates = commonDates(pool.map((x) => x.spark));
450 + }
451 + if (pool.length >= 2 && dates.length >= 2) {
452 + const maps = pool.map((x) => byDateMap(x.spark));
431 453 const points = trimPts(
432 454 dates.map((t) => ({
433 455 t,
@@ -438,13 +460,13 @@ export function buildEcoPayload(
438 460 volume = {
439 461 serie: {
440 462 id: "eco-vol",
441 − title: `Volume principal agrégé — ${withSpark.length} plateformes`,
463 + title: `Volume principal agrégé — ${pool.length} plateformes`,
442 464 kind: "area",
443 465 points,
444 466 },
445 − note: `Somme, jour par jour, des indicateurs principaux publiés par ${withSpark.length} plateformes (${withSpark
467 + note: `Somme, jour par jour, des indicateurs principaux publiés par ${pool.length} plateformes (${pool
446 468 .map((x) => x.p.site.wordmark)
447 − .join(", ")}), sur les ${dates.length} dates communes à leurs séries.`,
469 + .join(", ")}), sur les ${dates.length} dates communes à leurs séries quotidiennes.`,
448 470 };
449 471 }
450 472 }
@@ -452,7 +474,7 @@ export function buildEcoPayload(
452 474 // 2) croissance comparée des 4 plus grosses plateformes (indice base 100)
453 475 let growthIndex: EcoPayload["growthIndex"] = null;
454 476 {
455 − const top4 = withSpark.slice(0, 4);
477 + const top4 = withDaily.slice(0, 4);
456 478 if (top4.length >= 2) {
457 479 const dates = commonDates(top4.map((x) => x.spark));
458 480 if (dates.length >= 2) {
@@ -488,7 +510,7 @@ export function buildEcoPayload(
488 510 // 3) volume empilé par plateforme dans le temps (top 5)
489 511 let stacked: EcoPayload["stacked"] = null;
490 512 {
491 − const top5 = withSpark.slice(0, 5);
513 + const top5 = withDaily.slice(0, 5);
492 514 if (top5.length >= 2) {
493 515 const dates = commonDates(top5.map((x) => x.spark));
494 516 if (dates.length >= 2) {
495 517