Skip to content

Commit

Permalink
Merge pull request #171 from ansforge/auto/model_tracker
Browse files Browse the repository at this point in the history
[ AUTO ] Model Tracker
  • Loading branch information
romainfd committed Sep 20, 2024
2 parents af53738 + 90ff1c6 commit b5a5b65
Show file tree
Hide file tree
Showing 427 changed files with 3,123 additions and 2,486 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/generate-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ jobs:
with:
java-version: '11'
distribution: 'temurin'

- name: Update the RC-DE schema in json_schema2xsd resources
working-directory: ./csv_parser
if: steps.filter.outputs.parsing_required == 'true'
run: |
rm -f ./json_schema2xsd/src/main/resources/RC-DE.schema.json
cp ./out/RC-DE/RC-DE.schema.json ./json_schema2xsd/src/main/resources/RC-DE.schema.json
- name: Generate XSDs
working-directory: ./csv_parser/json_schema2xsd
Expand Down Expand Up @@ -134,6 +141,10 @@ jobs:

- name: Apply license
run: ./gradlew licenseFormat

- name: Delete old xml files
run: |
find ./src/main/resources/sample/examples -name "*.xml" -type f -delete
- name: Generate XML files
run: |
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/generate-specs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Build Web Specs

on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
type: string
required: true
description: Tag to be used for the generated image (should be a valid version)

jobs:
build_web_specs:
runs-on: ubuntu-latest
env:
RELEASE_NAME: "${{ github.ref_name || github.event.inputs.tag }}"

steps:
- name: Check release name matches version pattern
run: |
PATTERN="^([0-9]+\.[0-9]+(\.[0-9]+)?)(-[A-Za-z0-9\.]+)*$"
if [[ $RELEASE_NAME =~ $PATTERN ]]; then
echo "The RELEASE_NAME '$RELEASE_NAME' is valid."
else
echo "The RELEASE_NAME '$RELEASE_NAME' is invalid."
exit 1
fi
- name: Checkout
uses: actions/checkout@v3

- name: Setup node env 🏗
uses: actions/setup-node@v3
with:
node-version: 20

- name: Cache node_modules 📦
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-generator

- name: Install AsyncAPI CLI and Generator 👨🏻‍💻
run: npm i -g @asyncapi/cli @asyncapi/generator

# Use AsyncAPI generator to generate the HTML bundle
- name: Generate docs for Hub Santé interface ⚙️
run: asyncapi generate fromTemplate csv_parser/out/hubsante.asyncapi.yaml @asyncapi/html-template -o web_specs/generated/ --force-write

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push specs website
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/hub-web-specs:${{ env.RELEASE_NAME }}
context: ./web_specs
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
name: build image on release
name: Build library package on release

on:
release:
types:
- published

jobs:
build-image:
build_library:
runs-on: ubuntu-latest
env:
RELEASE_NAME: ${{ github.ref_name}}

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Set up JDK
uses: actions/setup-java@v2
Expand All @@ -26,8 +26,8 @@ jobs:

- name: check release name matches version pattern
run: |
PATTERN="^([0-9]+\.[0-9]+(\.[0-9]+)?)(-[A-Za-z]+(-[0-9]+))?$"
PATTERN="^([0-9]+\.[0-9]+\.[0-9]+)(-[A-Za-z]+(-[0-9]+))?$"
if [[ $RELEASE_NAME =~ $PATTERN ]]; then
echo "The RELEASE_NAME '$RELEASE_NAME' is valid."
else
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ generator/classes/**
build/**
reportgeneratortool/**
coveragereport/**

web_specs/generated/**
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ jacocoTestReport {
test {
if (project.hasProperty('technical')) {
include '**/*Technical*.class'
} else exclude '**/*Technical*.class'
} else if (!project.hasProperty('allTests'))
exclude '**/*Technical*.class'
}

allprojects {
Expand Down
3 changes: 3 additions & 0 deletions csv_parser/auto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
NOMENCLATURE_IN_FOLDER="/Users/romainfouilland/Library/CloudStorage/OneDrive-SharedLibraries-ANS/Espace Projets - Espace Programme SI-SAMU/01 - Equipe projet/07 - Innovation et prospectif/12 - Hub Santé/17 - MDD/Nomenclatures/01 - Base interne/"
NOMENCLATURE_FOLDER="../nomenclature_parser/in/"
MODELS_IN_FILE="/Users/romainfouilland/Library/CloudStorage/OneDrive-SharedLibraries-ANS/Espace Projets - Espace Programme SI-SAMU/01 - Equipe projet/07 - Innovation et prospectif/12 - Hub Santé/17 - MDD/MDD - Hub Santé.xlsx"
TECHNICAL_MODELS_IN_FILE="/Users/romainfouilland/Library/CloudStorage/OneDrive-SharedLibraries-ANS/Espace Projets - Espace Programme SI-SAMU/01 - Equipe projet/07 - Innovation et prospectif/12 - Hub Santé/17 - MDD/MDD TECHNICAL - Hub Santé.xlsx"
MODELS_FILE="models/model.xlsx"
TECHNICAL_MODELS_FILE="models/model-technical.xlsx"
TRACKING_BRANCH_NAME="auto/model_tracker"
DATE=$(date +'%y.%m.%d %H:%M')
LOG_FILE="cron.log"
Expand All @@ -19,6 +21,7 @@ setup() {
cp -r "$NOMENCLATURE_IN_FOLDER" "$NOMENCLATURE_FOLDER"
echo "Copying models excel from OneDrive to local folder..."
cp "$MODELS_IN_FILE" "$MODELS_FILE"
cp "$TECHNICAL_MODELS_IN_FILE" "$TECHNICAL_MODELS_FILE"
}

# Function to check for changes in nomenclature folder and run nomenclatures generation
Expand Down
34 changes: 30 additions & 4 deletions csv_parser/csv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')

full_asyncapi = None
all_model_types = []
first_codeandlabel_name = ""
first_codeandlabel_properties = []

Expand Down Expand Up @@ -480,11 +481,11 @@ def type_matching(child):
"""Get the matching type for a given type name"""
typeName = child['Format (ou type)']
if typeName == 'date':
return 'string', r'\d{4}-\d{2}-\d{2}', None
return 'string', r'^\d{4}-\d{2}-\d{2}$', None
elif typeName == 'datetime':
return 'string', r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\-+]\d{2}:\d{2}', 'date-time'
return 'string', r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\-+]\d{2}:\d{2}$', 'date-time'
elif typeName == 'phoneNumber':
return 'string', r'tel:([#\+\*]|37000|00+)?[0-9]{2,15}', None
return 'string', r'^tel:([#\+\*]|37000|00+)?[0-9]{2,15}$', None
else:
if has_format_details(child, 'REGEX: '):
return typeName, child['Détails de format'][7:], None
Expand Down Expand Up @@ -560,6 +561,15 @@ def add_object_child_definition(parent, child, definitions):
'additionalProperties': is_source_message(childTrueTypeName),
'example': parentExamplePath + '/' + child['name'] + ('/0' if is_array(child) else '')
}
elif (childOriginalTypeName != "codeAndLabel"
and 'children' in child):
"""If this is not the first occurrence of the object
and it has children, then the model is incorrectly defined and we should throw an error and exit.
We make an exception for codeAndLabel"""
print(f"{Color.RED}ERROR: object '{childTrueTypeName}' is defined multiple times. ")
print(f"Make sure the object is not defined multiple times in the model.{Color.END}")
exit(1)

"""If this is the first codeAndLabel, we record its name, otherwise we copy the properties from the first
codeAndLabel to the current codeAndLabel"""
if childOriginalTypeName == "codeAndLabel":
Expand Down Expand Up @@ -680,6 +690,19 @@ def DFS(root, use_elem):

print(f'{Color.BOLD}{Color.UNDERLINE}{Color.PURPLE}Generating JSON schema...{Color.END}')
DFS(rootObject, build_json_schema)

"""Before dumping, we verify the json schema definitions to make sure no objects with no properties are defined.
This verification is skipped for RS-ERROR schema"""
if MODEL_TYPE != "error":
empty_object_errors = []
for key, definition in json_schema['definitions'].items():
if not definition['properties']:
empty_object_errors.append(f"{Color.RED}ERROR: object '{key}' is defined but has no properties.{Color.END}")
if empty_object_errors:
for error in empty_object_errors:
print(error)
print(f"{Color.RED}Make sure no empty objects are defined in the model.{Color.END}")
exit(1)
with open(f'out/{name}/{name}.schema.json', 'w', encoding='utf8') as outfile:
json.dump(json_schema, outfile, indent=4, ensure_ascii=False)
print('JSON schema generated.')
Expand Down Expand Up @@ -747,7 +770,7 @@ def json_schema_to_openapi_schema(json_schema):

# Adding current asyncapi schemas to full asyncapi schema
global full_asyncapi
if (full_asyncapi is None):
if full_asyncapi is None:
full_asyncapi = asyncapi_yaml
else:
full_asyncapi['components']['schemas'].update(asyncapi_yaml['components']['schemas'])
Expand Down Expand Up @@ -944,6 +967,9 @@ def def_to_table(name, definition, title='', doc=None, style='Medium Shading 1 A
else:
def_to_table(MODEL_NAME, json_schema, style=style).save(f'docx-styles/others/schema-{style}.docx')

# Add MODEL_TYPE to parsed schemas
all_model_types.append(MODEL_TYPE)


if __name__ == '__main__':
parser = argparse.ArgumentParser(
Expand Down
62 changes: 54 additions & 8 deletions csv_parser/json_schema2xsd/src/main/java/json_schema2xsd/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,34 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.*;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import java.nio.file.Files;
import java.util.*;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

public class App {
public static void main(String[] args) {
Logger logger = Logger.getLogger(App.class.getName());
List<String> regexErrors = new ArrayList<>();

for (String schema : Arrays.asList("EMSI", "RC-DE", "RC-EDA", "RC-REF", "RS-EDA", "RS-INFO", "GEO-RES", "GEO-REQ", "GEO-POS", "RS-ERROR", "RS-RI",
"RS-DR", "RS-RR", "RPIS", "RS-EDA-MAJ", "RS-SR", "TECHNICAL", "TECHNICAL_NOREQ")) {
"RS-DR", "RS-RR", "RPIS", "RS-EDA-MAJ", "RS-SR", "TECHNICAL", "TECHNICAL_NOREQ")) {
// Specify the path to your JSON schema file
String jsonSchemaResourcePath = "/" + schema + ".schema.json";

Expand Down Expand Up @@ -52,6 +61,14 @@ public static void main(String[] args) {
List<String> convertedEnums = new ArrayList<>();
convertEnumArraysToSimpleEnum(jsonNode, "", convertedEnums);

try {
// Convert regex values to be compatible with XML schema
// by removing ^ and $ characters from the beginning and end of the string
convertRegexValues(jsonNode);
} catch (RuntimeException e) {
regexErrors.add("Error converting regex values in schema " + schema + ": " + e.getMessage());
}

// Create converted reader
byte[] bytes = mapper.writeValueAsBytes(jsonNode);
InputStream converted = new ByteArrayInputStream(bytes);
Expand All @@ -76,11 +93,40 @@ public static void main(String[] args) {
// Write the generated XML schema to a file
writeDocumentToFile(xmlSchemaDoc, xsdFilePath);
} catch (Exception e) {
e.printStackTrace();
logger.severe("Error while attempting to process schema " + schema + ": " + e );
System.exit(1);
}
}
if (!regexErrors.isEmpty()) {
for (String error : regexErrors) {
logger.severe(error);
}
System.exit(1);
}
}

private static void convertRegexValues(JsonNode node) {
if (!node.isObject()) {
return;
}

if (node.has("pattern")) {
String pattern = node.get("pattern").asText();
if (pattern.startsWith("^") && pattern.endsWith("$")) {
((ObjectNode) node).put("pattern", pattern.substring(1, pattern.length() - 1));
} else {
// Patterns should always start with ^ and end with $, so if they don't, the model
// is incorrectly defined
throw new RuntimeException(node.get("title") + ": RegEx patterns should start with ^ and end with $.");
}
}

for (Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); fields.hasNext(); ) {
Map.Entry<String, JsonNode> entry = fields.next();
convertRegexValues(entry.getValue());
}
}

private static String getTargetNamespace(String schema) {
if (schema.equalsIgnoreCase("RC-DE")) {
return "urn:emergency:cisu:2.0";
Expand Down
Binary file added csv_parser/models/model-technical.xlsx
Binary file not shown.
Binary file modified csv_parser/models/model.xlsx
Binary file not shown.
12 changes: 6 additions & 6 deletions csv_parser/out/EMSI/EMSI.input.csv
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Dans le cadre d'une mission décrivant les opérations en cours :
- reprendre la nomenclature EMSI pour caractériser la mission en cours.",SAV/ASC,TYPE,1..1,,string,NOMENCLATURE: EMSI-MISSION.TYPE
,Texte libre,,,,,Contient des commentaires relatifs aux objectifs et moyens sollicités dans le cadre de la demande de concours. Les équipements supplémentaires souhaités ou le nom/ prénom des patients à prendre en charge peuvent être explicitement indiqués ici.,Besoin d'un grand véhicule car patients à prendre en charge volumineux. Ne pas utiliser les gyrophares en approche,FREETEXT,0..1,,string,
,Identifiant de la mission,,,,,"Contient un identifiant de demande de concours unique. Cet identifiant sera réutilisable par le partenaire pour répondre à cette demande.
Identifiant unique de la mission dans le système du partenaire la conduisant.",/FF#bb511708-b004-4d7e-8820-1d56c19f1823,ID,0..1,,string,
Identifiant unique de la mission dans le système du partenaire la conduisant.",/FF#bb511708-b004-4d7e-8820-1d56c19f1823,ID,1..1,,string,
,Identifiant de l'organisation propriétaire,,,,,"Indique l'organisation du partenaire concerné par la Demande de Concours (voir DSF). Le code CRRA ou le code du SIS peut être utilisé.
Indique l'organisation du service réalisant la mission.
Dans le cas d'une réponse, c'est l'organisation du concourant qui doit être indiquée.
Expand All @@ -160,7 +160,7 @@ Pour une réponse à demande de concours :
- Le nom de la mission est construit à partir de l'expression régulière suivante :
""#REPONSE_DEMANDE_CONCOURS#""{code_reponse}""#""
où le code_reponse peut prendre les valeurs ACCEPTE, REFUS, PARTIELLE, DIVERGENTE
- sinon libre",#DEMANDE_CONCOURS#CARENCE#Z.01.00.00#,NAME,0..1,,string,
- sinon libre",#DEMANDE_CONCOURS#CARENCE#Z.01.00.00#,NAME,1..1,,string,
,Status de la mission,,,,,"Les valeurs possibles avec lesquelles valoriser ce champ sont détaillées au sein d'une nomenclature EMSI
- ABO : mission refusée (ABOrted)
- CANCLD : mission annulée (CANCeLeD)**
Expand All @@ -170,7 +170,7 @@ où le code_reponse peut prendre les valeurs ACCEPTE, REFUS, PARTIELLE, DIVERGEN
- COM : événement terminé pour le métier (COMplete)
Le status de la mission et celui des RESSOURCE associées doit être cohérent et transcodable avec un status ANTARES (voir DSF)

Dans le cas d'un objet MISSION générique de réponse à demande de concours, le champ doit être valorisé à ""NST""",IPR,STATUS,0..1,,string,"ENUM: ABO, NST, CANCLD, COM, IPR, PAU"
Dans le cas d'un objet MISSION générique de réponse à demande de concours, le champ doit être valorisé à ""NST""",IPR,STATUS,1..1,,string,"ENUM: ABO, NST, CANCLD, COM, IPR, PAU"
,Début de la mission,,,,,"- Dans le cadre d'une réponse à Demande de Concours
Horraire cible pour l'arrivée sur les lieux décrites (peut diverger de l'horaire demandé)
- Dans le cadre d'une mission décrivant les opérations en cours :
Expand Down Expand Up @@ -205,15 +205,15 @@ Voir nomenclature EMSI associée",MAT/VEH/ROADVE/FRFGTN,RCLASS,1..n,,string,NOME
Le détail de fonctionnement de cette nomenclature est fournie en annexe.",,CHARACTERISTICS,0..n,,string,
,ID de la ressource,,,,,"Identifiant unique de la ressource dans le système du partenaire propriétaire. Les systèmes sont garants de l'unicité et de l'invariablité des ids de véhicule dans le temps. Ils peuvent se servir des ids dans les référentiels existants si ils sont uniques et stables.
Dans le cas d'un véhicule agrégé par un LRM (comme un SMUR), l'ID doit être valorisé avec son immatriculation.
Dans le cas d'un véhicule agrégé par NexSIS, l'ID fournit peut ne pas correspondre à une immatriculation.",77_45102#VSAV 1,ID,0..1,,string,voir description
Dans le cas d'un véhicule agrégé par NexSIS, l'ID fournit peut ne pas correspondre à une immatriculation.",77_45102#VSAV 1,ID,1..1,,string,voir description
,Identifiant de l'organisation propriétaire,,,,,"Identifiant de l'organisation à laquelle la ressource est rattachée (caserne, SAMU etc).
Se référer au DSF pour la structure normée des organisations.
Le format est le suivant {pays}.{domaine}.{organisation}.{structure interne}*.{unité fonctionnelle}*.
Dans le cas où le LRM/NexSIS sert d'aggrégateur pour des véhicules appartenant à un partenaire tiers (type ambulance privée), l'identifiant d'organisation permet d'identifier ce tiers.
A constituer par le rédacteur du présent EMSI pour être unique.",fr.fire.cgo440,ORG_ID,0..1,,string,
,Nom de la ressource,,,,,"Nom donné à la ressource par le partenaire. L'immatriculation peut être utilisée dans le nom courant des véhicules.
Dans le cas pompier, les véhicules sont nommés
Dans le cas d'équipier, cela peut être leur nom",VSAV 1 Nantes,NAME,0..1,,string,
Dans le cas d'équipier, cela peut être leur nom",VSAV 1 Nantes,NAME,1..1,,string,
,Description de la ressource,,,,,"Texte libre permettant de décrire la ressource où d'ajouter des précisions sur son engagement.
Permet aussi notamment de décrire des attributs librement pour la ressource.
Par exemple, pour un véhicule, sa plaque d'immatriculation.",,FREETEXT,0..1,,string,
Expand All @@ -240,7 +240,7 @@ Plusieurs positions du même type avec des horodatages différents peuvent être
- AVAILB : Lorsqu'une mission est terminée, une ressource redevient disponible
- RESRVD : Lorsque la ressource est réservée pour intervenir sur l'affaire mais pas encore engagée dans l'opération. Par exemple : un SMUR termine un autre transfert patient/victime avant de rejoindre une autre intervention : il est alors RESRVD
- IN_USE/MOBILE : à utiliser pour les véhicules et le personnel lorsqu'ils se déplaces
- IN_USE/ON_SCENE : à utiliser pour les véhicules et le personnel lorsqu'ils sont sur les lieux de l'affaire",IN_USE/MOBILE,STATUS,0..1,,string,"ENUM: AVAILB, UNAV, MAINTC, RESRVD, VIRTUAL, IN_USE/MOBILE, IN_USE/ON_SCENE"
- IN_USE/ON_SCENE : à utiliser pour les véhicules et le personnel lorsqu'ils sont sur les lieux de l'affaire",IN_USE/MOBILE,STATUS,1..1,,string,"ENUM: AVAILB, UNAV, MAINTC, RESRVD, VIRTUAL, IN_USE/MOBILE, IN_USE/ON_SCENE"
,Nationalité de la ressource,,,,,"Nationalité d'une ressource, réemployer ISO 3166-1-alpha-2 code elements.",FR,NATIONALITY,0..1,,string,
,Contacts,,,,,Liste de contacts utiles pour contacter par exemple le véhicule ou le personnel engagé dans l'opération.,,CONTACTS,0..n,X,contact,
,,Type de contact,,,,"Type de contact, voir énumération associée
Expand Down
Binary file modified csv_parser/out/EMSI/EMSI.schema.docx
Binary file not shown.
Loading

0 comments on commit b5a5b65

Please sign in to comment.