changeset 1:ab59c68b6985 draft default tip

planemo upload for repository https://github.com/Edinburgh-Genome-Foundry/Examples/blob/master/templates/template1.ipynb commit db4ac861e1d03fcdfe94321d858839124e493930-dirty
author tduigou
date Wed, 23 Jul 2025 09:47:44 +0000
parents a05746a5560f
children
files sculpt_sequences.py sculpt_sequences.xml test-data/test_json_workflow2.json
diffstat 3 files changed, 268 insertions(+), 116 deletions(-) [+]
line wrap: on
line diff
--- a/sculpt_sequences.py	Wed Jun 11 09:39:02 2025 +0000
+++ b/sculpt_sequences.py	Wed Jul 23 09:47:44 2025 +0000
@@ -1,11 +1,24 @@
 import argparse
+import json
+import sys
 import os
+import re
 import dnacauldron
 import dnachisel
 from Bio import SeqIO
 import proglog
 
 
+def smart_number(val):
+        try:
+            float_val = float(val)
+            if float_val.is_integer():
+                return int(float_val)
+            else:
+                return float_val
+        except ValueError:
+            raise ValueError(f"Invalid number: {val}")
+
 
 def sculpt_sequances(files_to_sculpt, file_name_mapping, outdir_scul, outdir_unscul, use_file_names_as_id,
                      avoid_patterns, enforce_gc_content, DnaOptimizationProblemClass,
@@ -21,33 +34,63 @@
 
     constraint_list = []
 
+ # avoid_patterns pearsing
     for pattern in avoid_patterns:
         constraint_list.append(dnachisel.AvoidPattern(pattern))
         print(f"AvoidPattern constraint: {pattern}")
 
+ # gc_constraints pearsing
     for constraint in enforce_gc_content:
-        try:
-            mini, maxi, window = map(float, constraint.split(';'))
-            constraint_list.append(dnachisel.EnforceGCContent(mini=mini, maxi=maxi, window=int(window)))
-            print(f"GC constraint: mini={mini}, maxi={maxi}, window={window}")
-        except ValueError:
-            print(f"Skipping invalid GC constraint: {constraint}")
+        constraints = [c.strip() for c in constraint.split('  ') if c.strip()] 
+        for line in constraints:
+            if not line:
+                continue
+            print(f"GC constraint: {line}")
+
+            try:
+                pairs = [kv.strip() for kv in line.split(',')]
+                params = {}
+                for pair in pairs:
+                    key, val = pair.split('=')
+                    key = key.strip()
+                    val = val.strip()
+                    params[key] = smart_number(val)
+
+                constraint_list.append(dnachisel.EnforceGCContent(**params))
+
+            except Exception as e:
+                print(f"Skipping invalid gc_constraints: {line} ({e})")
 
+ # hairpin_constraints pearsing
     for constraint in hairpin_constraints:
-        try:
-            stem_size, hairpin_window = map(int, constraint.split(';'))
-            constraint_list.append(dnachisel.AvoidHairpins(stem_size=stem_size, hairpin_window=hairpin_window))
-            print(f"Hairpin constraint: stem_size={stem_size}, hairpin_window={hairpin_window}")
-        except ValueError:
-            print(f"Skipping invalid hairpin constraint: {constraint}")
+        constraints = [c.strip() for c in constraint.split('  ') if c.strip()]
+        for line in constraints:
+            if not line:
+                continue
+            print(f"Hairpin constraint: {line}")
 
+            try:
+                pairs = [kv.strip() for kv in line.split(',')]
+                params = {}
+                for pair in pairs:
+                    key, val = pair.split('=')
+                    key = key.strip()
+                    val = val.strip()
+                    params[key] = smart_number(val)
+                    
+                constraint_list.append(dnachisel.AvoidHairpins(**params))
+
+            except Exception as e:
+                print(f"Skipping invalid hairpin_constraints: {line} ({e})")
+
+ # k_size pearsing
     for k_size in kmer_size:
         try:
             constraint_list.append(dnachisel.UniquifyAllKmers(k=int(k_size)))
             print(f"k-mer size is: {k_size}")
         except ValueError:
             print(f"Skipping invalid k-mer size: {k_size}")
-
+    
  #   refine the real record name dict
     if isinstance(file_name_mapping, str):
         file_name_mapping = dict(
@@ -134,16 +177,20 @@
     parser.add_argument("--outdir_unscul", required=True, help="unscul file dir")
     parser.add_argument("--use_file_names_as_id", type=lambda x: x.lower() == 'true', default=True,
                         help="Use file names as IDs (True/False)")
-    parser.add_argument("--avoid_patterns", required=True,
+    parser.add_argument("--avoid_patterns", required=False,
                         help="List of patterns to avoid (comma-separated, e.g., 'BsaI_site,BsmBI_site')")
-    parser.add_argument("--enforce_gc_content", required=True,
+    parser.add_argument("--gc_constraints", required=False,
                         help="GC content constraints as 'min;max;window' (space-separated, e.g., '0.3;0.7;100 0.1;0.3;100')")
-    parser.add_argument("--DnaOptimizationProblemClass", required=True,
+    parser.add_argument("--DnaOptimizationProblemClass", required=False,
                         help="the class to use for DnaOptimizationProblem")
-    parser.add_argument("--hairpin_constraints", required=True,
+    parser.add_argument("--hairpin_constraints", required=False,
                         help="Hairpin constraints as 'stem_size;window_size' (space-separated, e.g., '20;200 30;250')")
-    parser.add_argument("--kmer_size", required = True,
+    parser.add_argument("--kmer_size", required = False,
                         help="K-mer uniqueness size (e.g., '15')")
+    parser.add_argument("--json_params", required=False,
+                        help="JSON params for the tool")
+    parser.add_argument("--use_json_param", required=True,
+                            help="If use JSON as param source")
 
     return parser.parse_args()
 
@@ -151,26 +198,92 @@
 def extract_constraints_from_args(args):
     """Extract constraints directly from the command-line arguments."""
 
-    avoid_patterns = args.avoid_patterns.split(',')
+    split_pattern = r'(?:__cn__|\s{2,})'
+
+    # 1. Avoid patterns (split by any whitespace)
+    avoid_patterns = re.split(split_pattern, args.avoid_patterns.strip()) if args.avoid_patterns.strip() else []
+
+    # 2. Hairpin constraint: one dictionary (print as string later)
+    hairpin_constraints = re.split(split_pattern,args.hairpin_constraints.strip()) if args.hairpin_constraints.strip() else []
 
-    gc_constraints = [gc.strip() for gc in args.enforce_gc_content.split(' ')]
+    # 3. GC constraints: split by 2+ spaces or newlines
+    gc_constraints = re.split(split_pattern, args.gc_constraints.strip()) if args.gc_constraints.strip() else []
+
+    # 4. k-mer size: single value or list
+    kmer_size = [int(k.strip()) for k in args.kmer_size.strip().split(',') if k.strip()] if args.kmer_size.strip() else []
+    
+    # 5. DnaOptimizationProblemClass (as string)
+    DnaOptimizationProblemClass = args.DnaOptimizationProblemClass if args.DnaOptimizationProblemClass else None
+    
+    return avoid_patterns, hairpin_constraints, gc_constraints, kmer_size, DnaOptimizationProblemClass
+
 
-    kmer_size = [int(k) for k in args.kmer_size.split(',')]
+def load_constraints_from_json(json_path):
+    with open(json_path, 'r') as f:
+        params = json.load(f)
+
+    def split_lines(val):
+        if isinstance(val, str):
+            return [line.strip() for line in val.strip().split('\n') if line.strip()]
+        return val
 
-    hairpin_constraints = [hp.strip() for hp in args.hairpin_constraints.split(' ')]
+    avoid_patterns = split_lines(params.get("avoid_patterns", ""))
+    hairpin_constraints = split_lines(params.get("hairpin_constraints", ""))
+    gc_constraints = split_lines(params.get("gc_constraints", ""))
+    kmer_size = [int(k.strip()) for k in str(params.get("kmer_size", "")).split(',') if k.strip()]
+    DnaOptimizationProblemClass = params.get("DnaOptimizationProblemClass", None)
 
-    return avoid_patterns,  gc_constraints, kmer_size, hairpin_constraints
-
+    return {
+        "avoid_patterns": avoid_patterns,
+        "hairpin_constraints": hairpin_constraints,
+        "gc_constraints": gc_constraints,
+        "kmer_size": kmer_size,
+        "DnaOptimizationProblemClass": DnaOptimizationProblemClass
+    }
 
 if __name__ == "__main__":
     args = parse_command_line_args()
 
-    avoid_patterns, gc_constraints, kmer_size, hairpin_constraints = extract_constraints_from_args(args)
+    avoid_patterns, hairpin_constraints, gc_constraints, kmer_size, DnaOptimizationProblemClass = extract_constraints_from_args(args)
+
+    # Check if the flag --use_json_param is present and set to true
+    if "--use_json_param" in sys.argv:
+        use_json_index = sys.argv.index("--use_json_param") + 1
+        use_json = sys.argv[use_json_index].lower() == "true"
+    else:
+        use_json = False
+
+    # Now only check --json_params if use_json is True
+    if use_json:
+        if "--json_params" in sys.argv:
+            json_index = sys.argv.index("--json_params") + 1
+            json_file = sys.argv[json_index]
+            if json_file.lower() != "none":
+                json_constraints = load_constraints_from_json(json_file)
+                avoid_patterns = json_constraints["avoid_patterns"]
+                hairpin_constraints = json_constraints["hairpin_constraints"]
+                gc_constraints = json_constraints["gc_constraints"]
+                kmer_size = json_constraints["kmer_size"]
+                DnaOptimizationProblemClass = json_constraints["DnaOptimizationProblemClass"]
+
+    params = {
+        "files_to_sculpt": args.files_to_sculpt,
+        "file_name_mapping": args.file_name_mapping,
+        "outdir_unscul": args.outdir_unscul,
+        "outdir_scul": args.outdir_scul,
+        "use_file_names_as_id": args.use_file_names_as_id,
+        "avoid_patterns": avoid_patterns,
+        "hairpin_constraints": hairpin_constraints,
+        "gc_constraints": gc_constraints,
+        "kmer_size": kmer_size,
+        "DnaOptimizationProblemClass": DnaOptimizationProblemClass
+    }
+
 
     sculpt_sequances(
         args.files_to_sculpt, args.file_name_mapping,
         args.outdir_scul, args.outdir_unscul,
         args.use_file_names_as_id, avoid_patterns,
-        gc_constraints, args.DnaOptimizationProblemClass,
+        gc_constraints, DnaOptimizationProblemClass,
         kmer_size, hairpin_constraints
     )
--- a/sculpt_sequences.xml	Wed Jun 11 09:39:02 2025 +0000
+++ b/sculpt_sequences.xml	Wed Jul 23 09:47:44 2025 +0000
@@ -1,8 +1,8 @@
 <tool id="sculpt_sequences" name="Sculpt Sequences" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="21.09">
     <description>Optimize DNA sequences</description>
     <macros>
-        <token name="@VERSION_SUFFIX@">0</token>
-        <token name="@TOOL_VERSION@">0.1.0</token>
+        <token name="@VERSION_SUFFIX@">1</token>
+        <token name="@TOOL_VERSION@">0.2.0</token>
     </macros>
     <requirements>
         <requirement type="package" version="0.1.11">flametree</requirement>
@@ -18,59 +18,67 @@
         <requirement type="package" version="3.1.5">dna_features_viewer</requirement>
     </requirements>
     <command detect_errors="exit_code"><![CDATA[
-        #set avoid_patterns_list = []
-        #for $p in $rep_avoid_pattern
-            #silent avoid_patterns_list.append(str($p.avoid_pattern))
-        #end for
-        #set avoid_patterns = ','.join($avoid_patterns_list)
-        #set gc_constraints_list = []
-        #for $gc in $adv.rep_gc_constraints
-            #silent gc_constraints_list.append(str($gc.gc_min) + ';' + str($gc.gc_max) + ';' + str($gc.gc_window))
-        #end for
-        #set enforce_gc_content = ' '.join($gc_constraints_list)
-        #set hairpin_constraints_list = []
-        #for $h in $adv.rep_avoid_hairpins
-            #silent hairpin_constraints_list.append(str($h.hairpin_stem_size) + ';' + str($h.hairpin_window))
-        #end for
-        #set hairpin_constraints = ' '.join($hairpin_constraints_list)
+        #if str($json_use.use_json_param) == "false":
+            #set avoid_list = [line.strip() for line in str($avoid_patterns).strip().split('\n') if line.strip()]
+            #set avoid_patterns = ','.join($avoid_list)
+
+            #set hairpin_lines = [line.strip() for line in str($json_use.hairpin_constraints).strip().split('\n') if line.strip()]
+            #set hairpin_constraints = '__cn__'.join($hairpin_lines)
+
+            #set gc_lines = [line.strip() for line in str($json_use.gc_constraints).strip().split('\n') if line.strip()]
+            #set gc_constraints = '__cn__'.join($gc_lines)
+
+            #set kmer_size = $json_use.kmer_size
+
+         #else:
+            #set avoid_patterns = ''
+            #set hairpin_constraints = ''
+            #set gc_constraints = ''
+            #set kmer_size = ''
+        #end if
+
         #set genbank_file_paths = ','.join([str(f) for f in $genbank_files])
         #set $file_name_mapping = ",".join(["%s:%s" % (file.file_name, file.name) for file in $genbank_files])
+        
         mkdir 'outdir_scul' &&
         mkdir 'outdir_unscul' &&
         python '$__tool_directory__/sculpt_sequences.py' 
+            --use_json_param '$json_use.use_json_param'
             --files_to_sculpt '$genbank_file_paths'
             --file_name_mapping '$file_name_mapping' 
             --outdir_scul 'outdir_scul' 
             --outdir_unscul 'outdir_unscul' 
-            --use_file_names_as_id '$adv.use_file_names_as_ids' 
+            --use_file_names_as_id '$use_file_names_as_ids' 
             --avoid_patterns '$avoid_patterns'
-            --enforce_gc_content '$enforce_gc_content'
-            --DnaOptimizationProblemClass '$DnaOptimizationProblemClass'
-            --kmer_size '$adv.kmer_size'
+            --gc_constraints '$gc_constraints'
+            --kmer_size '$kmer_size'
             --hairpin_constraints '$hairpin_constraints'
+            #if $json_use.use_json_param:
+                --json_params '$json_use.json_params'
+            #else:
+                --json_params ''
+                --DnaOptimizationProblemClass '$json_use.DnaOptimizationProblemClass'
+            #end if
     ]]></command>
     <inputs>
         <param name="genbank_files" type="data_collection" collection_type="list" format="genbank" label="GenBank File(s)"/>
-        <param name="DnaOptimizationProblemClass" type="select" label="DnaOptimizationProblem Calss" help="select the assambly class">
-            <option value="DnaOptimizationProblem" selected="True">DnaOptimizationProblem</option>
-            <option value="CircularDnaOptimizationProblem">CircularDnaOptimizationProblem</option>
-        </param>
-        <repeat name="rep_avoid_pattern" title="Avoid Pattern Constraints">
-            <param name="avoid_pattern" type="text" label="Pattern to Avoid (e.g., BsaI_site and/or 8x1mer)" />
-        </repeat>
-        <section name="adv" title="Advanced Options" expanded="false">
-            <repeat name="rep_gc_constraints" title="Enforce GC Content Constraints">
-                <param name="gc_min" type="float" label="Minimum GC Content" value="0.1" optional="true"/>
-                <param name="gc_max" type="float" label="Maximum GC Content" value="0.9" optional="true"/>
-                <param name="gc_window" type="integer" label="GC Content Window Size" value="50" optional="true"/>
-            </repeat>
-            <param name="kmer_size" type="integer" label="K-mer Uniqueness Size" value="15" optional="true"/>
-            <repeat name="rep_avoid_hairpins" title="Avoid Hairpins">
-                <param name="hairpin_stem_size" type="integer" label="Stem Size" value="20" optional="true"/>
-                <param name="hairpin_window" type="integer" label="Window Size" value="200" optional="true"/>
-            </repeat>
-            <param name="use_file_names_as_ids" type="boolean" checked="True" label="Use File Names As Sequence IDs" />
-        </section>
+        <conditional name='json_use'>
+            <param name="use_json_param" type="boolean" checked="false" label="Use parameter from a JSON file" />
+            <when value="false">
+                <param name="DnaOptimizationProblemClass" type="select" label="DnaOptimizationProblem Calss" help="select the assambly class">
+                    <option value="DnaOptimizationProblem" selected="True">DnaOptimizationProblem</option>
+                    <option value="CircularDnaOptimizationProblem">CircularDnaOptimizationProblem</option>
+                </param>
+                <param name="avoid_patterns" type="text" area="true" label="Avoid Pattern Constraints" help="Each pattern on a line" />
+                <param name="hairpin_constraints" type="text" area="true" label="Hairpins Constraints" optional="true" help="e.g. (you can add others Hairpins Constraints on a new line): stem_size=20, hairpin_window=200"/>
+                <param name="gc_constraints" type="text" area="true" label="Enforce GC Content Constraints" optional="true" help="e.g. (you can add others Enforce GC Content Constraints on a new line): mini=0.3, maxi=0.7, window=100"/>
+                <param name="kmer_size" type="integer" label="K-mer Uniqueness Size" value="" optional="true" help="e.g.: 15"/>
+            </when>
+            <when value="true">
+                <param name="json_params" type="data" format="json" optional="true" label="JSON parameters file" help="Contains tool's parameters" />
+            </when>
+        </conditional>
+        <param name="use_file_names_as_ids" type="boolean" checked="True" label="Use File Names As Sequence IDs" />
     </inputs>   
     <outputs>
         <collection name="scul" type="list" label="scul group" >
@@ -82,7 +90,7 @@
     </outputs>
     <tests>
         <test> 
-        <!-- test for DnaOptimizationProblem -->
+            <!-- test for DnaOptimizationProblem -->
             <param name="genbank_files">
                 <collection type="list">
                     <element name="p15_PuroR" value="10_emma_genbanks/p15_PuroR.gb" />
@@ -97,36 +105,26 @@
                     <element name="HC_Amp_ccdB" value="10_emma_genbanks/HC_Amp_ccdB.gb" />
                 </collection>
             </param>
-            <param name="DnaOptimizationProblemClass" value="DnaOptimizationProblem" />
-            <param name="adv|use_file_names_as_ids" value="True" />
-            <!-- AvoidPatterns -->
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="BsaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="NotI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="XbaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="ClaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="8x1mer" />
-            </repeat>
-            <!-- EnforceGCContent -->
-            <repeat name="adv|rep_gc_constraints">
-                <param name="gc_min" value="0.1" />
-                <param name="gc_max" value="0.9" />
-            </repeat>
+            <conditional name="json_use">
+                <param name="use_json_param" value="false" />
+                <param name="DnaOptimizationProblemClass" value="DnaOptimizationProblem" />
+                <!-- AvoidPatterns -->
+                <param name="avoid_patterns" value="BsaI_site
+                NotI_site
+                XbaI_site
+                ClaI_site
+                8x1mer" />
+                <!-- EnforceGCContent -->
+                <param name="gc_constraints" value="mini=0.1, maxi=0.9, window=50"/>
+            </conditional>
+            <param name="use_file_names_as_ids" value="True" />
             <output_collection name="scul" count="10">
             </output_collection>
             <output_collection name="unscul" count="10">
             </output_collection>
         </test>
         <test> 
-        <!-- test for CircularDnaOptimizationProblem -->
+            <!-- test for CircularDnaOptimizationProblem -->
             <param name="genbank_files">
                 <collection type="list">
                     <element name="p15_PuroR" value="10_emma_genbanks/p15_PuroR.gb" />
@@ -141,29 +139,48 @@
                     <element name="HC_Amp_ccdB" value="10_emma_genbanks/HC_Amp_ccdB.gb" />
                 </collection>
             </param>
-            <param name="DnaOptimizationProblemClass" value="CircularDnaOptimizationProblem" />
-            <param name="adv|use_file_names_as_ids" value="True" />
-            <!-- AvoidPatterns -->
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="BsaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="NotI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="XbaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="ClaI_site" />
-            </repeat>
-            <repeat name="rep_avoid_pattern">
-                <param name="avoid_pattern" value="8x1mer" />
-            </repeat>
-            <!-- EnforceGCContent -->
-            <repeat name="adv|rep_gc_constraints">
-                <param name="gc_min" value="0.1" />
-                <param name="gc_max" value="0.9" />
-            </repeat>
+            <conditional name="json_use">
+                <param name="use_json_param" value="false" />
+                <param name="DnaOptimizationProblemClass" value="CircularDnaOptimizationProblem" />
+                <!-- AvoidPatterns -->
+                <param name="avoid_patterns" value="BsaI_site
+                NotI_site
+                XbaI_site
+                ClaI_site
+                8x1mer" />
+                <!-- EnforceGCContent -->
+                <param name="gc_constraints" value="mini=0.1, maxi=0.9
+                mini=0.3, maxi=0.7, window=50"/>
+                <param name="hairpin_constraints" value="stem_size=20, hairpin_window=200
+                stem_size=10, hairpin_window=100"/>
+            </conditional>
+            <param name="use_file_names_as_ids" value="True" />
+            <output_collection name="scul" count="10">
+            </output_collection>
+            <output_collection name="unscul" count="10">
+            </output_collection>
+        </test>
+        <test>
+            <!-- test json params -->
+            <param name="genbank_files">
+                <collection type="list">
+                    <element name="p15_PuroR" value="10_emma_genbanks/p15_PuroR.gb" />
+                    <element name="p9_PuroR" value="10_emma_genbanks/p9_PuroR.gb" />
+                    <element name="p15_Pup9_mTagBFP2roR" value="10_emma_genbanks/p9_mTagBFP2.gb" />
+                    <element name="p15_p9_BSDRPuroR" value="10_emma_genbanks/p9_BSDR.gb" />
+                    <element name="p8_Linker1" value="10_emma_genbanks/p8_Linker1.gb" />
+                    <element name="p7_L7Ae-Weiss" value="10_emma_genbanks/p7_L7Ae-Weiss.gb" />
+                    <element name="p6_Nt-IgKLsequence" value="10_emma_genbanks/p6_Nt-IgKLsequence.gb" />
+                    <element name="p6_Kozak-ATG" value="10_emma_genbanks/p6_Kozak-ATG.gb" />
+                    <element name="p4_Kt-L7Ae-Weiss" value="10_emma_genbanks/p4_Kt-L7Ae-Weiss.gb" />
+                    <element name="HC_Amp_ccdB" value="10_emma_genbanks/HC_Amp_ccdB.gb" />
+                </collection>
+            </param>
+            <conditional name="json_use">
+                <param name="use_json_param" value="true" />
+                <param name="json_params" value="test_json_workflow2.json" />
+            </conditional>
+            <param name="use_file_names_as_ids" value="True" />
             <output_collection name="scul" count="10">
             </output_collection>
             <output_collection name="unscul" count="10">
@@ -179,14 +196,20 @@
 **Parameters**:
 ---------------
 * **GenBank File(s)**: List of GenBank files to be processed.
+* **Use parameter from a JSON file**: 
+    Yes/No parameter to indicate if user want to set parameter manually or using a json file
+    If Yes, user should provide a JSON file contains all parameters
 * **DnaOptimizationProblem Class**: 
     - "DnaOptimizationProblem": is the class to define and solve an optimization problems. Its methods implement all the solver logics.
     - "CircularDnaOptimizationProblem": is a variant of DnaOptimizationProblem whose optimization algorithm assumes that the sequence is circular.
 * **Avoid Pattern Constraints**: is a sequence design rules that can be used as constraints. It define pattern(s) to avoid during problem optimisation.
     This can include enzyme sites like "BsaI_site", "NotI_site", "XbaI_site"... `enzyme dict <https://github.com/biopython/biopython/blob/master/Bio/Restriction/Restriction_Dictionary.py>`_ . Custom patterns are also supported, such as "5x3mer" means "any 5 consecutive 3-nucleotide sequences — typically 5 unique 3-mers in a row.
-* **Enforce GC Content Constraints**: Define acceptable GC content ranges. For example min: 0.4, max: 0.6, window: 50 represents a 40–60% GC content requirement within a 50-base window.
+* **Enforce GC Content Constraints**:
+    Define acceptable GC content ranges. For example min: 0.4, max: 0.6, window: 50 represents a 40–60% GC content requirement within a 50-base window.
+    (Parameters: `EnforceGCContent_params <https://edinburgh-genome-foundry.github.io/DnaChisel/ref/builtin_specifications#enforcegccontent>`_ )
 * **Avoid Hairpins**: Avoid Hairpin patterns as defined by the IDT guidelines.
     A hairpin is defined by a sequence segment which has a reverse complement “nearby” in a given window.
+    (Parameters: `AvoidHairpins_params <https://edinburgh-genome-foundry.github.io/DnaChisel/ref/builtin_specifications#avoidhairpins>`_ ).
 * **K-mer Uniqueness Size**: Avoid sub-sequence of length k with homologies elsewhere.
 * **Use File Names As Sequence IDs**: Recommended if the GenBank file names represent the fragment names.
     ]]></help>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/test_json_workflow2.json	Wed Jul 23 09:47:44 2025 +0000
@@ -0,0 +1,16 @@
+{
+    "DnaOptimizationProblemClass":"DnaOptimizationProblem",
+    "avoid_patterns": "BsaI_site\nNotI_site\nXbaI_site\nClaI_site\n8x1mer",
+    "hairpin_constraints": "stem_size=20, hairpin_window=200\nstem_size=10, hairpin_window=100",
+    "gc_constraints": "mini=0.1, maxi=0.9\nmini=0.3, maxi=0.7, window=50",
+    "kmer_size": "15",
+    "assembly_plan_name": "Type2sRestrictionAssembly",
+    "topology": "circular",
+    "enzyme": "auto",
+    "execution": "true",
+    "db_uri": "postgresql://postgres:RK17@localhost:5432/test_fragments_db",
+    "table": "sample",
+    "fragment_column": "fragment",
+    "sequence_column": "sequence",
+    "annotation_column": "annotation"
+}
\ No newline at end of file