Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
57320ff
first implem to get a quick review on the structure.
ttdm Dec 3, 2025
645663e
feat: define proper name, title, description for each schema.
ttdm Dec 3, 2025
c2b1f38
chore: remore old json schema
ttdm Dec 3, 2025
1f1fe1b
feat: move the scripts in sources, create github actions
ttdm Feb 16, 2026
f5b3f6f
chore: auto-generate schemas from source [skip ci]
github-actions[bot] Feb 16, 2026
225423c
feat: simplify extensions with the working generation
ttdm Feb 16, 2026
578f65a
chore: auto-generate schemas from source [skip ci]
github-actions[bot] Feb 16, 2026
f089353
chore: remove exemples
ttdm Feb 16, 2026
1c226dd
fix: assert version path
ttdm Feb 19, 2026
ff93e48
test: testing a wrong value in a json field to test the Ci frictionl…
ttdm Feb 19, 2026
e1a0732
chore: auto-generate schemas from source [skip ci]
github-actions[bot] Feb 19, 2026
e0348d7
chore: reseting to a proper exemple value
ttdm Feb 19, 2026
f43f83f
chore: auto-generate schemas from source [skip ci]
github-actions[bot] Feb 19, 2026
41d6553
feat: merger improvements, test improvement; small code quality impr…
ttdm Feb 19, 2026
d487442
chore: keep cleaning the code
ttdm Feb 19, 2026
5034df1
chore: move the builder main entry point on top of its class; move co…
ttdm Feb 24, 2026
347808e
feat: all small improvements from the review
ttdm Mar 20, 2026
cff5e51
feat: create a proper datapackage which link towards all table-schemas
ttdm Mar 20, 2026
68fdbb8
feat: add path into schemas, clarify what to_table_schema does
ttdm Mar 20, 2026
62bf8c6
refactor(schema_builder): make the repo_root injectable
David-Guillot Apr 8, 2026
bf72731
feat(schema_example_generator): move generated examples
David-Guillot Apr 8, 2026
4256243
test: initial testing
David-Guillot Apr 8, 2026
75b1d71
chore(ci): run tests before building schemas
David-Guillot Apr 8, 2026
6ec805d
feat: centralize all constants in a file, generate the current schema…
ttdm Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 24 additions & 35 deletions .github/workflows/assert_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,46 @@
def check(obj, version, parents=""):
"""
This functions recursively parses all fields in the schema looking for
version names that would not be the same as the one mentionned
version names that would not be the same as the one mentioned
in the 'version' field
"""
errors = []
# if field is a string, we check for a potential version
if isinstance(obj, str):
tmp = re.search(pattern, obj)
if tmp and tmp[0] != version:
errors += [(parents, tmp[0])]
version_match = re.search(pattern, obj)
if version_match and version_match[0] != version:
errors += [(parents, version_match[0])]
# if field is a list, we check every item
elif isinstance(obj, list):
for idx, k in enumerate(obj):
errors += check(k, version, parents=parents + f"[{str(idx)}]")
for idx, item in enumerate(obj):
errors += check(item, version, parents=parents + f"[{str(idx)}]")
# if field is a dict, we check every value
elif isinstance(obj, dict):
for k in obj:
for key in obj:
# not checking the fields
if k != "fields":
if key != "fields":
errors += check(
obj[k],
obj[key],
version,
parents=parents + "." + k if parents else k
parents=parents + "." + key if parents else key
)
return errors


to_check = []
SCHEMA_CORE_PATH = "schema/core/schema-core.json"

if "schema.json" in os.listdir():
to_check.append("schema.json")
assert os.path.isfile(SCHEMA_CORE_PATH)

elif "datapackage.json" in os.listdir():
with open("datapackage.json", "r") as f:
datapackage = json.load(f)
for r in datapackage["resources"]:
to_check.append(r["schema"])
with open(SCHEMA_CORE_PATH, "r") as f:
schema = json.load(f)
version = schema["version"]

else:
raise Exception("No required file found")

for schema_path in to_check:
with open(schema_path, "r") as f:
schema = json.load(f)
version = schema["version"]

errors = check(schema, version)
if errors:
message = (
f"Versions are mismatched within the schema '{schema['name']}', "
f"expected version '{version}' but:"
)
for e in errors:
message += f"\n- {e[0]} has version '{e[1]}'"
raise Exception(message)
errors = check(schema, version)
if errors:
message = (
f"Versions are mismatched within the schema '{schema['name']}', "
f"expected version '{version}' but:"
)
for error_location, error_version in errors:
message += f"\n- {error_location} has version '{error_version}'"
raise Exception(message)
68 changes: 68 additions & 0 deletions .github/workflows/build_and_validate_schemas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Build and Validate Schemas

on:
push:
branches:
- master
- feat/**
paths:
- "schema/**/*.json"
- "src/**/*.py"
- "requirements.txt"
pull_request:
paths:
- "schema/**/*.json"
- "src/**/*.py"
- "requirements.txt"

jobs:
build-and-validate:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run tests
run: python -m unittest tests/test_*.py

- name: Build schemas from source
run: python3 src/build_schemas.py

- name: Check for changes
id: verify_diff
run: |
git diff --quiet build/ || echo "changed=true" >> $GITHUB_OUTPUT

- name: Commit and push if changes detected
if: steps.verify_diff.outputs.changed == 'true' && github.event_name == 'push'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add build/
git commit -m "chore: auto-generate schemas from source [skip ci]"
git push

- name: Validate generated schemas
run: python3 src/validate_schemas.py
29 changes: 0 additions & 29 deletions .github/workflows/frictionless_tests.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
*.backup
.coverage
.python-version
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ Ce fichier répertorie les changements entre différentes versions du schéma.
## Version 0.1.0 - 2025-09-19

Publication initiale.

## Version 0.2.0 - 2026-02-XX

Publication du code dédié à générer de multiples schémas et publication d'une première version temporaire dédiée aux entreprises.
155 changes: 155 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Guide d'utilisation

## Workflow automatisé

**GitHub Actions génère et valide automatiquement les schémas** lorsque sont détextées des modifications dans `schema/` lors d'un push.

Vous n'avez qu'à modifier les fichiers sources dans `schema/core/` et `schema/extensions/` et commit le tout dans une branche puis la push sur github.

## Modifier un schéma

1. Ouvrir le fichier JSON concerné (core ou extension), ex: `schema/extensions/{category}/{name}.json`
Catégory étant limité à "cible" ou "usage".
(Autoriser plus d'option, nécessiterait d'introduire plus d'éléments de configuration, notamment pour pouvoir automatiquement savoir quelles combinaisons générer entre les différents catégories et les différents éléments de chaque catégorie.)

2. Ajouter un champ dans le tableau `fields` :

```json
{
"name": "nom_du_champ",
"title": "Titre du champ",
"description": "Description",
"type": "string",
"example": "valeur d'exemple",
"constraints": {
"required": true,
"maxLength": 500
}
}
```

3. Commit et push - GitHub Actions régénère et valide automatiquement les schémas

## Ajouter une extension (cible ou usage)

Le repo génère automatiquement toutes les combinaisons d'usages pour chaque cible.

Pour ajouter une nouvelle cible ou un nouvel usage :
1. Créer un fichier JSON dans `schema/extensions/cible/` ou `schema/extensions/usage/`
2. Suivre le format des fichiers existants
3. Commit et push - les schémas combinés sont générés automatiquement

# Development Guide

## Quick Setup

```bash
# Environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# Generate schemas and example files (optional - automated via GitHub Actions)
python3 src/build_schemas.py

# Validate (optional - automated via GitHub Actions)
python3 src/validate_schemas.py
```

## Architecture

The repo uses a modular datapackage structure:

- **Core schema** (`schema/core/schema-core.json`) - Base fields required for all schemas
- **Extensions** - Optional fields for specific use cases (usage & target audience)
- **Build script** - Generates schemas for all combinations of usage for each target

```
schema/
├── core/schema-core.json
└── extensions/
├── usage/
│ ├── communication.json
│ ├── pilotage.json
│ └── activation.json
└── cible/
├── associations.json
├── particuliers.json
├── professionnels.json
└── secteur-public.json
```

## Common Tasks

### Add a Field

1. Open the relevant JSON file (core or extension), ex: `schema/extensions/{category}/{name}.json`
2. Add to the `fields` array:

```json
{
"name": "field_name",
"title": "Field Title",
"description": "Description",
"type": "string",
"example": "example value"
}
```

3. Commit and push - GitHub Actions will automatically regenerate and validate schemas
4. (Optional) Test locally: `python3 src/build_schemas.py && python3 src/validate_schemas.py`

### Create an Extension

The simplest way is to copy an existing extension for reference.

The written code automatically handles new targets or usages but needs to be edited if a new category of extensions is created.

Currently, there is no generic logic to handle a new type of extension.
In particular, the associations between extensions in this new category should be defined, and the allowed combinations between extension categories should be defined.

### Resolve Field Conflicts

When fields with the same name have different types, you'll get warnings. Resolve by:

**Option 1: Rename** - Use different names in different extensions

```json
// In usage/pilotage.json
{"name": "budget_total_alloue", "type": "number"}

// In cible/individuals.json
{"name": "budget_per_person", "type": "string"}
```

**Option 2: Harmonize** - Use the same type everywhere

```json
{ "name": "budget", "type": "number" }
```

**Future enhancements:**
Handle priority fields to allow overriding some fields. (Identified use case: make a field mandatory within an extension).

## Testing

```bash
# Validate a specific schema
frictionless validate build/schemas/dispositif-aide-pilotage.json

# Validate data against a schema
frictionless validate data.csv --schema build/schemas/dispositif-aide.json

# Validate all schemas
python3 scripts/validate_schemas.py
```

When working on the code, the evolutions should be tested with
```bash
python -m unittest tests/test_*.py
```
## References

- [Frictionless Data Specs](https://specs.frictionlessdata.io/)
- [Table Schema Spec](https://specs.frictionlessdata.io/table-schema/)
- [Data Package Spec](https://specs.frictionlessdata.io/data-package/)
Empty file added build/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions build/exemples/exemple-complet.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cibles,date_cloture,date_mise_a_jour,date_ouverture,description,eligibilite,eligibilite_geographique,eligibilite_geographique_exclusions,id,porteurs,programmes_parents,promesse,titre,types_aides,url_source
professionnels|associations,2025-10-15T15:00:00Z,2025-10-15T15:00:00Z,2025-10-15T15:00:00Z,"Le Futsal fait l'objet aujourd'hui d'un véritable plan de développement à la FFF L'une des orientations fédérales vise à doter les clubs amateurs de terrains spécifiques extérieurs. En effet, l'accès aux salles et aux gymnases étant très difficile pour les clubs de football, compte tenu de la saturation des créneaux d'utilisation, la FFF propose aux collectivités locales, propriétaires des installations sportives, une solution extérieure à moindre coût. Elle doit permettre aux clubs de football de développer une activité Futsal à travers leur école de football ou une section spécifique, sans remettre en cause l'activité même des sports exclusivement de salle. Cette solution technique, qui dans la majorité des cas pourra se substituer à un plateau multisports existant et obsolète, offre de plus aux collectivités davantage de possibilités d'utilisation, notamment pour la pratique scolaire. La FFF souhaite donc accompagner la construction de terrains dédiés au sein des clubs amateurs, avec pour objectif de permettre la diversification de leur offre de pratique en proposant une activité nouvelle à leurs licenciés.","• Le porteur du projet doit être, soit un club affilié à la F.F.F., soit une collectivité locale en collaboration avec un club support affilié à la F.F.F. ;
• Le terrain doit être conforme au cahier des charges technique fédéral relatif à la réalisation d'un terrain de Futsal extérieur – Edition avril 2017;
• La date de commencement des travaux ne doit pas être antérieure de plus de 3 mois à celle du dépôt du dossier au District d'appartenance ;
• L'équipement projeté doit être situé obligatoirement au sein d'un complexe sportif utilisé par le club support dont au moins une installation est classée au niveau 6 minimum ou adossé à un gymnase classé futsal 4 minimum équipé de deux vestiaires de minimum chacun 14 m2, douches et sanitaires avec un accès direct piétons ;
• Le porteur de projet doit impérativement présenter un projet d'utilisation des installations envisagées dans le respect des attentes de la F.F.F. ;
• Le maître d'ouvrage doit réaliser son opération dans un délai de 24 mois à compter de la date d'attribution de la subvention par le Bureau Exécutif de la Ligue du Football Amateur",PAYS-99100,REG-84|DEP-01|COM-83141,65d5b6c7-102c-4440-ac3b-768f708edc0a,"[{ nom: 'ADEME', siren: '385290309', roles: [ 'instructeur' ]}, { nom: 'BPIFRANCE (BPIFRANCE)', siren: '320252489', roles: [ 'financeur' ]}]",Fonds Chaleur|Fonds vert 2024,Développez votre stratégie bas carbone et mettez en œuvre votre plan de transition,Accélérateur décarbonation,information|financement,https://agirpourlatransition.ademe.fr/entreprises/aides-financieres/2023/audit-energetique-industrie
7 changes: 7 additions & 0 deletions build/exemples/par_schema/exemple-dispositif-aide.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
id,titre,promesse,description,eligibilite,types_aides,porteurs,programmes_parents,url_source,cibles,eligibilite_geographique,eligibilite_geographique_exclusions,date_ouverture,date_cloture,date_mise_a_jour
65d5b6c7-102c-4440-ac3b-768f708edc0a,Accélérateur décarbonation,Développez votre stratégie bas carbone et mettez en œuvre votre plan de transition,"Le Futsal fait l'objet aujourd'hui d'un véritable plan de développement à la FFF L'une des orientations fédérales vise à doter les clubs amateurs de terrains spécifiques extérieurs. En effet, l'accès aux salles et aux gymnases étant très difficile pour les clubs de football, compte tenu de la saturation des créneaux d'utilisation, la FFF propose aux collectivités locales, propriétaires des installations sportives, une solution extérieure à moindre coût. Elle doit permettre aux clubs de football de développer une activité Futsal à travers leur école de football ou une section spécifique, sans remettre en cause l'activité même des sports exclusivement de salle. Cette solution technique, qui dans la majorité des cas pourra se substituer à un plateau multisports existant et obsolète, offre de plus aux collectivités davantage de possibilités d'utilisation, notamment pour la pratique scolaire. La FFF souhaite donc accompagner la construction de terrains dédiés au sein des clubs amateurs, avec pour objectif de permettre la diversification de leur offre de pratique en proposant une activité nouvelle à leurs licenciés.","• Le porteur du projet doit être, soit un club affilié à la F.F.F., soit une collectivité locale en collaboration avec un club support affilié à la F.F.F. ;
• Le terrain doit être conforme au cahier des charges technique fédéral relatif à la réalisation d'un terrain de Futsal extérieur – Edition avril 2017;
• La date de commencement des travaux ne doit pas être antérieure de plus de 3 mois à celle du dépôt du dossier au District d'appartenance ;
• L'équipement projeté doit être situé obligatoirement au sein d'un complexe sportif utilisé par le club support dont au moins une installation est classée au niveau 6 minimum ou adossé à un gymnase classé futsal 4 minimum équipé de deux vestiaires de minimum chacun 14 m2, douches et sanitaires avec un accès direct piétons ;
• Le porteur de projet doit impérativement présenter un projet d'utilisation des installations envisagées dans le respect des attentes de la F.F.F. ;
• Le maître d'ouvrage doit réaliser son opération dans un délai de 24 mois à compter de la date d'attribution de la subvention par le Bureau Exécutif de la Ligue du Football Amateur",information|financement,"[{ nom: 'ADEME', siren: '385290309', roles: [ 'instructeur' ]}, { nom: 'BPIFRANCE (BPIFRANCE)', siren: '320252489', roles: [ 'financeur' ]}]",Fonds Chaleur|Fonds vert 2024,https://agirpourlatransition.ademe.fr/entreprises/aides-financieres/2023/audit-energetique-industrie,professionnels|associations,PAYS-99100,REG-84|DEP-01|COM-83141,2025-10-15T15:00:00Z,2025-10-15T15:00:00Z,2025-10-15T15:00:00Z
Loading
Loading