• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inclusion-numerique / coop-mediation-numerique / d8de6e05-24d8-4280-aae0-2589f3d40ae1

19 May 2026 04:35PM UTC coverage: 9.989% (+3.0%) from 7.008%
d8de6e05-24d8-4280-aae0-2589f3d40ae1

Pull #497

circleci

marc-gavanier
feat: improve structure fusion scoring and review export

Significantly reduces the manual review burden by detecting more
true duplicates automatically and avoiding false positives.

Scoring improvements (detect-duplicate-structures, generate-structures-action-plan):
- Treat clusters of type 'mixte' like 'doublon_certain' with per-pair
  scoring (instead of bulk verification_manuelle), uncovering hundreds
  of auto/probable fusions previously hidden in mixed clusters.
- Boost address score to 1.0 when one normalized address is contained
  in the other (e.g. "Lupino" vs "LUPINO PARVIS NOTRE DAME VICTOIRE").
- Add address abbreviations: VC (voie communale), RT (route), ZA, ZI, CH.
- Redistribute geo weight when coords are unavailable, OR when address
  strongly indicates the same place (>=0.85): prevents penalizing
  structures with missing or erroneous coords.
- Normalize "commune de/du", "mairie de/du", "ville de/du" to a single
  "ville" canonical token so variants match.
- Detect "service keywords" (EPN, médiathèque, CCAS, France services,
  MJC, etc.): when one name has such a keyword and the other does not,
  they are distinct entities even with shared SIRET/address. Disables
  the address-contained heuristic and keeps geo in the score.

Sync resilience (findOrCreateStructure):
- After strict siret+codeInsee miss, fall back to siret-only with
  normalized contained-name match. This catches Dataspace structures
  whose codeInsee diverges from the coop's, without merging an EPN
  with its parent town hall (asymmetric-service-keyword guard).

Review output:
- generate-structures-action-plan: structures-fusion-review.csv now
  uses cluster-grouped format (CIBLE + sources + empty line between
  clusters, sorted by ascending score), matching the existing format
  Tim uses for his manual reviews.
- export-duplicate-sirets: cluster-grouped CSV (empty line between
  SIRETs) and exclude empty-string siret. Enrich each row with
  nom_api, adresse_api, corre... (continued)
Pull Request #497: feat: improve structure fusion scoring and review export

688 of 10878 branches covered (6.32%)

Branch coverage included in aggregate %.

26 of 153 new or added lines in 4 files covered. (16.99%)

1111 existing lines in 95 files now uncovered.

2111 of 17142 relevant lines covered (12.31%)

1.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/web/src/utils/encodeSerializableState.ts
1
import pako from 'pako'
2
import superjson from 'superjson'
3

4
/**
5
 * Utility functions to encode and decode form state for safe transmission via URL.
6
 *
7
 * This module provides two functions, `encodeSerializableState` and `decodeSerializableState`,
8
 * which are intended to be used for serializing form state to a compressed string
9
 * and deserializing it back to its original form. This is particularly useful for passing
10
 * non-sensitive form data through URLs, ensuring the data remains unreadable to users.
11
 *
12
 * Example use case:
13
 * - Encode form state into a compressed string and append it as a query parameter in a URL.
14
 * - Decode the compressed string from the URL to prefill the form with the original data.
15
 */
16

17
export type EncodedState<T> = string & { __encodedStateType: T }
18

19
/**
20
 * Converts a Uint8Array to a url safe Base64 string.
21
 */
UNCOV
22
const uint8ArrayToBase64 = (uint8Array: Uint8Array): string => {
×
UNCOV
23
  const binaryString = [...uint8Array]
×
UNCOV
24
    .map((byte) => String.fromCodePoint(byte))
×
25
    .join('')
26
  return btoa(binaryString)
27
    .replaceAll('+', '-') // Replace "+" with "-"
28
    .replaceAll('/', '_') // Replace "/" with "_"
29
    .replace(/=+$/, '') // Remove "=" padding
30
}
31

32
/**
33
 * Converts a url safe Base64 string back to a Uint8Array.
34
 */
UNCOV
35
const base64ToUint8Array = (base64String: string): Uint8Array => {
×
UNCOV
36
  const binaryString = atob(
×
37
    base64String
38
      .replaceAll('-', '+') // Replace "-" with "+"
39
      .replaceAll('_', '/'), // Replace "_" with "/"
40
  )
UNCOV
41
  return Uint8Array.from(binaryString, (char) => {
×
UNCOV
42
    const code = char.codePointAt(0)
×
UNCOV
43
    if (code === undefined) {
×
44
      throw new Error('Invalid character in Base64 string')
×
45
    }
UNCOV
46
    return code
×
47
  })
48
}
49

UNCOV
50
export const encodeSerializableState = <T>(state: T): EncodedState<T> => {
×
UNCOV
51
  const jsonString = superjson.stringify(state)
×
UNCOV
52
  const compressed = pako.deflate(jsonString)
×
53

UNCOV
54
  return uint8ArrayToBase64(compressed) as EncodedState<T> // Base64-URL-safe encoding
×
55
}
56

UNCOV
57
export const decodeSerializableState = <T>(
×
58
  encodedState: EncodedState<T>,
59
  defaultValue: T, // Needed if decoding fails
60
): T => {
UNCOV
61
  try {
×
UNCOV
62
    const compressed = base64ToUint8Array(encodedState) // Base64-URL-safe decoding
×
UNCOV
63
    const jsonString = pako.inflate(compressed, { to: 'string' })
×
UNCOV
64
    return superjson.parse<T>(jsonString)
×
65
  } catch {
UNCOV
66
    return defaultValue
×
67
  }
68
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc