This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 418b3f3  feat: add create-election.py script and election.yaml.sample
418b3f3 is described below

commit 418b3f3a0a9ecbc6944840cb1dbc2ce7ed499c88
Author: Greg Stein <[email protected]>
AuthorDate: Tue Feb 3 03:21:51 2026 -0600

    feat: add create-election.py script and election.yaml.sample
    
    Co-authored-by: aider (openrouter/x-ai/grok-code-fast-1) <[email protected]>
---
 v3/server/bin/create-election.py   | 123 +++++++++++++++++++++++++++++++++++++
 v3/server/bin/election.yaml.sample |  28 +++++++++
 2 files changed, 151 insertions(+)

diff --git a/v3/server/bin/create-election.py b/v3/server/bin/create-election.py
new file mode 100644
index 0000000..d3235f1
--- /dev/null
+++ b/v3/server/bin/create-election.py
@@ -0,0 +1,123 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import argparse
+import datetime
+import pathlib
+import logging
+import yaml
+
+import steve.election
+import steve.persondb
+
+_LOGGER = logging.getLogger(__name__)
+
+THIS_DIR = pathlib.Path(__file__).resolve().parent
+DB_FNAME = THIS_DIR.parent / 'steve.db'
+
+# Supported vote types
+VALID_VTYPES = {'yna', 'stv'}
+
+def parse_datetime(dt_str):
+    """Parse ISO datetime string to Unix timestamp."""
+    if not dt_str:
+        return None
+    dt = datetime.datetime.fromisoformat(dt_str)
+    return int(dt.timestamp())
+
+def validate_issue(issue):
+    """Validate an issue dict from YAML."""
+    if 'vtype' not in issue or issue['vtype'] not in VALID_VTYPES:
+        raise ValueError(f"Invalid vtype: {issue.get('vtype')}")
+    if issue['vtype'] == 'stv':
+        kv = issue.get('kv', {})
+        if not all(k in kv for k in ['version', 'labelmap', 'seats']):
+            raise ValueError("STV issue missing required kv fields: version, 
labelmap, seats")
+        if not isinstance(kv['seats'], int) or kv['seats'] <= 0:
+            raise ValueError("STV seats must be a positive integer")
+    return issue
+
+def main(yaml_file):
+    with open(yaml_file, 'r') as f:
+        data = yaml.safe_load(f)
+
+    # Extract election data
+    election_data = data.get('election', {})
+    title = election_data.get('title')
+    owner_pid = election_data.get('owner_pid')
+    authz = election_data.get('authz')
+    open_at = parse_datetime(election_data.get('open_at'))
+    close_at = parse_datetime(election_data.get('close_at'))
+
+    if not title or not owner_pid:
+        raise ValueError("Election must have title and owner_pid")
+
+    # Extract issues
+    issues = data.get('issues', [])
+    for issue in issues:
+        validate_issue(issue)
+
+    # Extract voters tag (placeholder: assume "members" means all persons)
+    eligible_voters = data.get('eligible_voters')
+    if eligible_voters != 'members':
+        raise ValueError("Only 'members' is supported for eligible_voters")
+
+    # Start transaction for safety
+    pdb = steve.persondb.PersonDB(DB_FNAME)
+    pdb.db.conn.execute('BEGIN TRANSACTION')
+
+    try:
+        # Create election
+        election = steve.election.Election.create(DB_FNAME, title, owner_pid, 
authz, open_at, close_at)
+        _LOGGER.info(f'Created election[E:{election.eid}]: "{title}" by owner 
"{owner_pid}"')
+
+        # Add issues
+        for issue_data in issues:
+            iid = election.add_issue(
+                issue_data['title'],
+                issue_data.get('description'),
+                issue_data['vtype'],
+                issue_data.get('kv') if issue_data['vtype'] == 'stv' else None
+            )
+            _LOGGER.info(f'Added issue[I:{iid}] to election[E:{election.eid}]')
+
+        # Add voters: All persons in persondb to all issues
+        all_persons = pdb.list_persons()
+        for person in all_persons:
+            election.add_voter(person.pid)
+        _LOGGER.info(f'Added {len(all_persons)} voters to 
election[E:{election.eid}]')
+
+        pdb.db.conn.execute('COMMIT')
+        _LOGGER.info(f'Election[E:{election.eid}] fully created from 
{yaml_file}')
+
+    except Exception as e:
+        pdb.db.conn.execute('ROLLBACK')
+        _LOGGER.error(f'Failed to create election from {yaml_file}: {e}')
+        raise
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter
+    )
+    parser.add_argument(
+        'yaml_file',
+        help='Path to the YAML file defining the election.',
+    )
+    args = parser.parse_args()
+    main(args.yaml_file)
diff --git a/v3/server/bin/election.yaml.sample 
b/v3/server/bin/election.yaml.sample
new file mode 100644
index 0000000..e5277f6
--- /dev/null
+++ b/v3/server/bin/election.yaml.sample
@@ -0,0 +1,28 @@
+# Sample YAML file for defining an election to be created via 
create-election.py
+# Copy this file, edit as needed, and run: python create-election.py 
<your_file.yaml>
+
+election:
+  title: "Board Election 2024"
+  owner_pid: "alice"  # Apache ID of the election owner
+  authz: "pmc"  # Optional authorization level
+  open_at: "2024-01-01T00:00:00"  # Optional ISO datetime for opening
+  close_at: "2024-01-31T23:59:59"  # Optional ISO datetime for closing
+
+issues:
+  - title: "Approve Budget"
+    description: "Vote yes/no/abstain on the proposed budget."
+    vtype: "yna"  # Yes/No/Abstain vote type
+
+  - title: "Elect Board Members"
+    description: "Rank candidates for board seats using STV."
+    vtype: "stv"  # Single Transferable Vote type
+    kv:
+      version: 1  # KV format version
+      labelmap:
+        a: "Alice"
+        b: "Bob"
+        c: "Carlos"
+        d: "David"
+      seats: 3  # Number of seats to elect
+
+eligible_voters: "members"  # Placeholder: all persons in the database are 
eligible

Reply via email to