Files
award/CLAUDE.md
Joerg 8a1a5804ff feat: implement DCL ADIF parser and service integration
- Add shared ADIF parser utility (src/backend/utils/adif-parser.js)
  - parseADIF(): Parse ADIF format into QSO records
  - parseDCLResponse(): Parse DCL's JSON response format
  - normalizeBand() and normalizeMode(): Standardize band/mode names

- Implement DCL service (src/backend/services/dcl.service.js)
  - fetchQSOsFromDCL(): Fetch from DCL API (ready for API availability)
  - parseDCLJSONResponse(): Parse example payload format
  - syncQSOs(): Update existing QSOs with DCL confirmations
  - Support DCL-specific fields: DCL_QSL_RCVD, DCL_QSLRDATE, DARC_DOK, MY_DARC_DOK

- Refactor LoTW service to use shared ADIF parser
  - Remove duplicate parseADIF, normalizeBand, normalizeMode functions
  - Import from shared utility for consistency

- Tested with example DCL payload
  - Successfully parses all 6 QSOs
  - Correctly extracts DCL confirmation data
  - Handles ADIF format with <EOR> delimiters

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 11:49:36 +01:00

6.9 KiB

Default to using Bun instead of Node.js.

  • Use bun <file> instead of node <file> or ts-node <file>
  • Use bun test instead of jest or vitest
  • Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild
  • Use bun install instead of npm install or yarn install or pnpm install
  • Use bun run <script> instead of npm run <script> or yarn run <script> or pnpm run <script>
  • Use bunx <package> <command> instead of npx <package> <command>
  • Bun automatically loads .env, so don't use dotenv.

APIs

  • Bun.serve() supports WebSockets, HTTPS, and routes. Don't use express.
  • bun:sqlite for SQLite. Don't use better-sqlite3.
  • Bun.redis for Redis. Don't use ioredis.
  • Bun.sql for Postgres. Don't use pg or postgres.js.
  • WebSocket is built-in. Don't use ws.
  • Prefer Bun.file over node:fs's readFile/writeFile
  • Bun.$ls instead of execa.

Testing

Use bun test to run tests.

import { test, expect } from "bun:test";

test("hello world", () => {
  expect(1).toBe(1);
});

Frontend

Use HTML imports with Bun.serve(). Don't use vite. HTML imports fully support React, CSS, Tailwind.

Server:

import index from "./index.html"

Bun.serve({
  routes: {
    "/": index,
    "/api/users/:id": {
      GET: (req) => {
        return new Response(JSON.stringify({ id: req.params.id }));
      },
    },
  },
  // optional websocket support
  websocket: {
    open: (ws) => {
      ws.send("Hello, world!");
    },
    message: (ws, message) => {
      ws.send(message);
    },
    close: (ws) => {
      // handle close
    }
  },
  development: {
    hmr: true,
    console: true,
  }
})

HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. <link> tags can point to stylesheets and Bun's CSS bundler will bundle.

<html>
  <body>
    <h1>Hello, world!</h1>
    <script type="module" src="./frontend.tsx"></script>
  </body>
</html>

With the following frontend.tsx:

import React from "react";
import { createRoot } from "react-dom/client";

// import .css files directly and it works
import './index.css';

const root = createRoot(document.body);

export default function Frontend() {
  return <h1>Hello, world!</h1>;
}

root.render(<Frontend />);

Then, run index.ts

bun --hot ./index.ts

For more information, read the Bun API docs in node_modules/bun-types/docs/**.mdx.

Project: Quickawards by DJ7NT

Quickawards is a amateur radio award tracking application that calculates progress toward various awards based on QSO (contact) data.

Award System Architecture

The award system is JSON-driven and located in award-definitions/ directory. Each award has:

  • id: Unique identifier (e.g., "dld", "dxcc")
  • name: Display name
  • description: Short description
  • caption: Detailed explanation
  • category: Award category ("dxcc", "darc", etc.)
  • rules: Award calculation logic

Award Rule Types

  1. entity: Count unique entities (DXCC countries, states, grid squares)

    • entityType: What to count ("dxcc", "state", "grid", "callsign")
    • target: Number required for award
    • filters: Optional filters (band, mode, etc.)
    • displayField: Optional field to display
  2. dok: Count unique DOK (DARC Ortsverband Kennung) combinations

    • target: Number required
    • confirmationType: "dcl" (DARC Community Logbook)
    • Counts unique (DOK, band, mode) combinations
    • Only DCL-confirmed QSOs count
  3. points: Point-based awards

    • stations: Array of {callsign, points}
    • target: Points required
    • countMode: "perStation", "perBandMode", or "perQso"
  4. filtered: Filtered version of another award

    • baseRule: The base entity rule
    • filters: Additional filters to apply
  5. counter: Count QSOs or callsigns

Key Files

Backend Award Service: src/backend/services/awards.service.js

  • getAllAwards(): Returns all available award definitions
  • calculateAwardProgress(userId, award, options): Main calculation function
  • calculateDOKAwardProgress(userId, award, options): DOK-specific calculation
  • calculatePointsAwardProgress(userId, award, options): Point-based calculation
  • getAwardEntityBreakdown(userId, awardId): Detailed entity breakdown
  • getAwardProgressDetails(userId, awardId): Progress with details

Database Schema: src/backend/db/schema/index.js

  • QSO fields include: darcDok, dclQslRstatus, dclQslRdate
  • DOK fields support DLD award tracking
  • DCL confirmation fields separate from LoTW

Award Definitions: award-definitions/*.json

  • Add new awards by creating JSON definition files
  • Add filename to loadAwardDefinitions() file list in awards.service.js

DLD Award Implementation (COMPLETED)

The DLD (Deutschland Diplom) award was recently implemented:

Definition: award-definitions/dld.json

{
  "id": "dld",
  "name": "DLD",
  "description": "Deutschland Diplom - Confirm 100 unique DOKs on different bands/modes",
  "caption": "Contact and confirm stations with 100 unique DOKs (DARC Ortsverband Kennung) on different band/mode combinations.",
  "category": "darc",
  "rules": {
    "type": "dok",
    "target": 100,
    "confirmationType": "dcl",
    "displayField": "darcDok"
  }
}

Implementation Details:

  • Function: calculateDOKAwardProgress() in src/backend/services/awards.service.js (lines 173-268)
  • Counts unique (DOK, band, mode) combinations
  • Only DCL-confirmed QSOs count (dclQslRstatus === 'Y')
  • Each unique DOK on each unique band/mode counts separately
  • Returns worked, confirmed counts and entity breakdowns

Database Fields Used:

  • darcDok: DOK identifier (e.g., "F03", "P30", "G20")
  • band: Band (e.g., "80m", "40m", "20m")
  • mode: Mode (e.g., "CW", "SSB", "FT8")
  • dclQslRstatus: DCL confirmation status ('Y' = confirmed)
  • dclQslRdate: DCL confirmation date

Documentation: See docs/DOCUMENTATION.md for complete documentation including DLD award example.

Adding New Awards

To add a new award:

  1. Create JSON definition in award-definitions/
  2. Add filename to loadAwardDefinitions() in src/backend/services/awards.service.js
  3. If new rule type needed, add calculation function
  4. Add type handling in calculateAwardProgress() switch statement
  5. Add type handling in getAwardEntityBreakdown() if needed
  6. Update documentation in docs/DOCUMENTATION.md
  7. Test with sample QSO data

Confirmation Systems

  • LoTW (Logbook of The World): ARRL's confirmation system

    • Fields: lotwQslRstatus, lotwQslRdate
    • Used for DXCC, WAS, VUCC, most awards
  • DCL (DARC Community Logbook): DARC's confirmation system

    • Fields: dclQslRstatus, dclQslRdate
    • Required for DLD award
    • German amateur radio specific

Recent Commits

  • c982dcd: feat: implement DLD (Deutschland Diplom) award
  • 322ccaf: docs: add DLD (Deutschland Diplom) award documentation