{"id":36885780,"url":"https://github.com/nextzlog/ats4","last_synced_at":"2026-01-12T15:29:53.480Z","repository":{"id":37743223,"uuid":"86975523","full_name":"nextzlog/ats4","owner":"nextzlog","description":"automatic acceptance \u0026 tabulation system for amateur radio contests","archived":false,"fork":false,"pushed_at":"2025-12-26T12:36:05.000Z","size":3486,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-28T00:45:45.573Z","etag":null,"topics":["amateur-radio","contest","tabulation","web-application"],"latest_commit_sha":null,"homepage":"https://nextzlog.dev/ats4","language":"Scala","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nextzlog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-04-02T09:15:56.000Z","updated_at":"2025-12-26T12:36:08.000Z","dependencies_parsed_at":"2024-04-08T17:38:23.308Z","dependency_job_id":"2827e6e0-5fc3-4fef-86b4-a238879bcf89","html_url":"https://github.com/nextzlog/ats4","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/nextzlog/ats4","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextzlog%2Fats4","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextzlog%2Fats4/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextzlog%2Fats4/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextzlog%2Fats4/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nextzlog","download_url":"https://codeload.github.com/nextzlog/ats4/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextzlog%2Fats4/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28340816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amateur-radio","contest","tabulation","web-application"],"created_at":"2026-01-12T15:29:52.909Z","updated_at":"2026-01-12T15:29:53.475Z","avatar_url":"https://github.com/nextzlog.png","language":"Scala","readme":"ATS-4: Amateur-Radio Contest Administration System\n====\n\n![image](https://img.shields.io/badge/sbt-1.10.0-red.svg)\n![image](https://img.shields.io/badge/Java-JDK17-red.svg)\n![image](https://img.shields.io/badge/Scala-2.13-orange.svg)\n![image](https://img.shields.io/badge/JRuby-9.3-orange.svg)\n![image](https://img.shields.io/badge/PlayFramework-2.9-blueviolet.svg)\n![image](https://img.shields.io/badge/license-GPL3-darkblue.svg)\n![badge](https://github.com/nextzlog/ats4/actions/workflows/build.yaml/badge.svg)\n\nATS-4 is an Automatic Acceptance \u0026 Tabulation System for Amateur-Radio Contests, based on [QxSL](https://github.com/autodyne/qxsl).\nFeel free to visit [ALLJA1 ATS-4](https://allja1.org).\n\n## Features\n\n- provides a web interface for contest-log acceptance.\n- verifies the uploaded logs according to the contest rules implemented in Ruby.\n- supports many contests including [UEC](https://www.ja1zgp.com/uectest_public_info), [ALLJA1](https://ja1zlo.u-tokyo.org/allja1), [REAL-TIME](https://ja1zlo.u-tokyo.org/rt/rt1.html), [VUS](https://www.ja1zgp.com/uectest-vus_public_info), [QRP](https://www2.jaqrp.org/contests/), [AREA-1 AM](https://6mnet.jp/mannaka/blog), etc.\n\n## Documents\n\n- [Scaladoc](https://nextzlog.github.io/ats4/api)\n- [ATS-4 (PDF)](https://nextzlog.dev/ats4.pdf)\n\n## Quick Start\n\nDocker image is available.\nPaste the entire following script into the terminal and run it.\n\n```sh\necho -n 'enter mail hostname: '\nread host\n\necho -n 'enter mail username: '\nread user\n\necho -n 'enter mail password: '\nread pass\n\ncat \u003c\u003c EOS \u003e docker-compose.yaml\nversion: '3'\nservices:\n  ATS4:\n    image: ghcr.io/nextzlog/ats4:master\n    ports:\n    - 9000:9000\n    volumes:\n    - ./ats/data:/ats/data\n    - ./ats/logs:/ats/logs\n    command: /ats/bin/ats4\n    environment:\n      TZ: Asia/Tokyo\n      ATS4_MAIL_HOST: $host\n      ATS4_MAIL_USER: $user\n      ATS4_MAIL_PASS: $pass\n      ATS4_MAIL_MOCK: false\n      ATS4_RULE_FILE: /rules/ats.rb\n  www:\n    image: nginx:latest\n    ports:\n    - 80:80\n    volumes:\n    - ./proxy.conf:/etc/nginx/conf.d/default.conf\nEOS\n\necho -n 'enter server domain: '\nread name\n\ncat \u003c\u003c EOS \u003e proxy.conf\nserver {\n  server_name $name;\n  location / {\n    proxy_pass http://ATS4:9000;\n    location ~ /admin {\n      allow 127.0.0.1;\n      deny all;\n    }\n  }\n}\nEOS\n\ndocker compose up -d\n```\n\nThen, point your browser to http://localhost and verify that ATS-4 is running.\n\n## Configuration\n\nFirst, create `docker-compose.yaml` as follows:\n\n```yaml\nversion: '3'\nservices:\n  ATS4:\n    image: ghcr.io/nextzlog/ats4:master\n    ports:\n    - 9000:9000\n    volumes:\n    - ./ats/data:/ats/data\n    - ./ats/logs:/ats/logs\n    command: /ats/bin/ats4\n    environment:\n      TZ: Asia/Tokyo\n      ATS4_MAIL_HOST: $host\n      ATS4_MAIL_USER: $user\n      ATS4_MAIL_PASS: $pass\n      ATS4_MAIL_MOCK: false\n      ATS4_RULE_FILE: /rules/ats.rb\n  www:\n    image: nginx:latest\n    ports:\n    - 80:80\n    volumes:\n    - ./proxy.conf:/etc/nginx/conf.d/default.conf\n```\n\nThen, follow the instructions below.\n\n### Proxy\n\nCreate `proxy.conf` as follows:\n\n```nginx\nserver {\n  server_name localhost;\n  location / {\n    proxy_pass http://ATS4:9000;\n    location ~ /admin {\n      allow 127.0.0.1;\n      deny all;\n    }\n  }\n}\n```\n\nMake sure that unauthorized clients cannot access administration pages under `/admin`.\nExpose port 80 of the container to the internet so that the administration page cannot be accessed.\n\n### Email\n\nConfigure environment variables in `docker-compose.yaml` as follows:\n\n```yaml\nenvironment:\n  ATS4_MAIL_HOST: $host\n  ATS4_MAIL_USER: $user\n  ATS4_MAIL_PASS: $pass\n  ATS4_MAIL_MOCK: false\n```\n\nModify the settings properly.\n\n### Regulation\n\nConfigure environment variables in `docker-compose.yaml` as follows:\n\n```yaml\nenvironment:\n  ATS4_RULE_FILE: /rules/ats.rb\n# ATS4_RULE_FILE: /rules/1am.rb\n# ATS4_RULE_FILE: /rules/ja1.rb\n# ATS4_RULE_FILE: /rules/uec.rb\n```\n\nOf course, you can specify different rules by mounting external Ruby files into the container.\nSee [`ats.rb`](conf/rules/ats.rb) for example.\n\n### Run\n\nFinally, create a container as follows:\n\n```sh\n$ docker compose up -d\n```\n\nAccess 80 port of the container.\n\n### Stop\n\nStop and remove the container as follows:\n\n```sh\n$ docker compose down\n```\n\n### Update\n\nPull the latest image as follows:\n\n```sh\n$ docker pull ghcr.io/nextzlog/ats4:master\n```\n\n## Development Mode\n\nYou can change Scala code and configuration without restarting by starting ATS-4 in development mode as follows:\n\n```sh\n$ sbt run\n```\n\nThen, access http://localhost:9000/admin/shell to develop contest rules interactively.\nYou can test the scoring algorithm by attaching QSO data to the web form.\n\n## Stream API\n\nATS-4 provides the streaming API for the [REAL-TIME CONTEST](http://ja1zlo.u-tokyo.org/rt/rt1.html).\n\n### Registration\n\nContest participants will register their account information with ATS-4 in advance.\nATS-4 returns a security key (UUID) by sending a `GET` request to `http://localhost:8873?id=\u003cUUID\u003e`.\nClients may retrieve the key by listening on the 8873 port and access `/agent/\u003cUUID\u003e`.\n\n### Upstream\n\nWhen the contest starts, the client always connects to the server via WebSocket.\nEach time a participant contacts another participant on air, the client sends the difference in the QSO records to the server.\nMessages from the clients to the server must follow the format below.\n\n|position|field                 |\n|--------|----------------------|\n|1st byte|number of QSOs deleted|\n|sequence|header of the QSO data|\n|sequence|QSO entities to delete|\n|sequence|QSO entities to append|\n\nThe second and subsequent bytes of the messages are formatted as a single electronic log file.\nThe format must be officially supported by the [QXSL](https://github.com/autodyne/qxsl) library.\n\n### Downstream\n\nThe server receives the QSO records, scores it, wait a few seconds, and then notifies all clients of the score update.\nJSON messages from the server to the clients are formatted as follows:\n\n```JSON\n{\n  \"14MHz\": [\n    {\"call\": \"JA1ZLO\", \"score\": 200, \"total\": 2200},\n    {\"call\": \"JA1YWX\", \"score\": 100, \"total\": 2100}\n  ]\n}\n```\n\n### Demonstration\n\nA simple WebSocket client for ATS-4 may be written as follows:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang='ja'\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eATS-4\u003c/title\u003e\n    \u003cscript type=\"application/javascript\" src=\"client.js\"\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eStreaming Demo\u003c/h1\u003e\n    \u003ctextarea cols='160' rows='30' id='QSOs'\u003e\u003c/textarea\u003e\n    \u003cp\u003e\n      \u003clabel\u003eDelete \u003cinput type='number' id='trim' min='0' max='255' value='0'\u003eQSOs,\u003c/label\u003e\n      \u003clabel\u003eSubmission Key: \u003cinput type='text' id='UUID' placeholder='/agent/UUID'\u003e\u003c/label\u003e\n      \u003cbutton type='button' onclick='access();'\u003eAccess\u003c/button\u003e\n      \u003cbutton type='button' onclick='submit();'\u003eSubmit\u003c/button\u003e\n    \u003c/p\u003e\n    \u003cdiv id='messages'\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe JavaScript program referenced may be written as follows:\n\n```js\nlet sock;\nfunction access() {\n  const uuid = document.getElementById('UUID').value;\n  sock = new WebSocket('ws://localhost:9000' + uuid);\n  sock.binaryType = 'arraybuffer';\n  sock.onmessage = function(msg) {\n    const decoder = new TextDecoder();\n    const data = decoder.decode(new Uint8Array(msg.data));\n    const text = document.createTextNode(data);\n    const node = document.createElement('div');\n    document.getElementById('messages').appendChild(node);\n    node.appendChild(text);\n  };\n}\nfunction submit() {\n  const encoder = new TextEncoder();\n  const QSOs = document.getElementById('QSOs').value;\n  const trim = document.getElementById('trim').value;\n  const data = new TextEncoder().encode(QSOs);\n  const full = new (data.constructor)(data.length + 1);\n  full[0] = parseInt(trim);\n  full.set(data, 1);\n  sock.send(full);\n}\n```\n\n## Contribution\n\nFeel free to make issues at [nextzlog/todo](https://github.com/nextzlog/todo).\nFollow [@nextzlog](https://twitter.com/nextzlog) on Twitter.\n\n## License\n\n### Author\n\n[無線部開発班](https://nextzlog.dev)\n\n- JG1VPP\n- JJ2ULU\n- JH1GEB\n- JE6MDL\n- JO4EFC\n- JJ1IBY\n- JS2FVO\n\n### Clauses\n\n- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\n- This program is distributed in the hope that it will be useful, but **without any warranty**; without even the implied warranty of **merchantability or fitness for a particular purpose**.\nSee the GNU General Public License for more details.\n\n- You should have received a copy of the GNU General Public License along with this program.\nIf not, see \u003chttp://www.gnu.org/licenses/\u003e.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnextzlog%2Fats4","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnextzlog%2Fats4","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnextzlog%2Fats4/lists"}