cards/docs/marketplace/seed/periodensystem-elemente/design/build_cards.py
Till JS 9a07454b75 seed: 3 Cardecky-Decks v1.0.0 + Audit-Trail im Repo
Drei Cardecky-Decks live im lokalen Marketplace, mit komplettem
Audit-Trail unter docs/marketplace/seed/:

| Deck                          | Karten | Lizenz    |
|-------------------------------|--------|-----------|
| geografie-welt-top30          |    30  | CC0-1.0   |
| english-a2-grundwortschatz    |   500  | CC-BY-4.0 |
| periodensystem-elemente       |   118  | CC0-1.0   |

648 Karten gesamt = 1296 FSRS-Reviews. Alle drei via /cards-deck-
Skill 5-Stage-Pipeline (Plan, Recherche, Design, Validate, Publish).
Bulk-Mode mit Python-Heredoc-Generator, Server-Side atomic-Insert
in <1s pro Deck.

Pro Deck im Audit-Trail:
- plan.md (Subtopic-Boundaries, Streitfälle vorab)
- research/sources.md (3-8 nummerierte Quellen)
- research/notes.md (Recherche-Notes, Streitfall-Auflösungen)
- design/build_cards.py (deterministischer Generator mit Sanity-
  Checks gegen Front-Duplikate)
- design/cards.jsonl (atomic Output, 1 Karte/Zeile)
- design/outline.md (Subtopic-Counts + F-Range)
- validate/report.md (5 Standard-Checks alle ✓)
- publish/deck.json + cards.log (Server-Response + Round-Trip)

CONTENT_PLAN §8 Phase-1-Seed-Liste: 3/20 done.

README.md erklärt die seed/-Konvention für künftige Cardecky-Decks
sowie das Update-Protokoll bei PR-Merges aus dem Marketplace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:49:05 +02:00

171 lines
5.5 KiB
Python

"""
Bulk-Mode-Generator für periodensystem-elemente.
Output: cards.jsonl mit 118 basic-reverse-Karten (Symbol ↔ Name).
"""
import json
import os
import random
OUT = os.path.join(os.path.dirname(__file__), 'cards.jsonl')
# (Z, Symbol, Name, [extra_source_refs])
# Default-source ist [1] (IUPAC) + [2] (DE-Wikipedia). Streitfälle
# bekommen extra [3] (GDCh) oder [4] (Tennessin-IUPAC-2016).
ELEMENTS = [
(1, 'H', 'Wasserstoff', []),
(2, 'He', 'Helium', []),
(3, 'Li', 'Lithium', []),
(4, 'Be', 'Beryllium', []),
(5, 'B', 'Bor', []),
(6, 'C', 'Kohlenstoff', []),
(7, 'N', 'Stickstoff', []),
(8, 'O', 'Sauerstoff', []),
(9, 'F', 'Fluor', []),
(10, 'Ne', 'Neon', []),
(11, 'Na', 'Natrium', []),
(12, 'Mg', 'Magnesium', []),
(13, 'Al', 'Aluminium', []),
(14, 'Si', 'Silicium', []),
(15, 'P', 'Phosphor', []),
(16, 'S', 'Schwefel', []),
(17, 'Cl', 'Chlor', []),
(18, 'Ar', 'Argon', []),
(19, 'K', 'Kalium', []),
(20, 'Ca', 'Calcium', []),
(21, 'Sc', 'Scandium', []),
(22, 'Ti', 'Titan', []),
(23, 'V', 'Vanadium', []),
(24, 'Cr', 'Chrom', []),
(25, 'Mn', 'Mangan', []),
(26, 'Fe', 'Eisen', []),
(27, 'Co', 'Cobalt', [3]), # IUPAC-Schreibung statt Kobalt
(28, 'Ni', 'Nickel', []),
(29, 'Cu', 'Kupfer', []),
(30, 'Zn', 'Zink', []),
(31, 'Ga', 'Gallium', []),
(32, 'Ge', 'Germanium', []),
(33, 'As', 'Arsen', []),
(34, 'Se', 'Selen', []),
(35, 'Br', 'Brom', []),
(36, 'Kr', 'Krypton', []),
(37, 'Rb', 'Rubidium', []),
(38, 'Sr', 'Strontium', []),
(39, 'Y', 'Yttrium', []),
(40, 'Zr', 'Zirconium', []),
(41, 'Nb', 'Niob', []),
(42, 'Mo', 'Molybdän', []),
(43, 'Tc', 'Technetium', []),
(44, 'Ru', 'Ruthenium', []),
(45, 'Rh', 'Rhodium', []),
(46, 'Pd', 'Palladium', []),
(47, 'Ag', 'Silber', []),
(48, 'Cd', 'Cadmium', []),
(49, 'In', 'Indium', []),
(50, 'Sn', 'Zinn', []),
(51, 'Sb', 'Antimon', []),
(52, 'Te', 'Tellur', []),
(53, 'I', 'Iod', [3]), # IUPAC-Schreibung statt Jod
(54, 'Xe', 'Xenon', []),
(55, 'Cs', 'Caesium', [3]), # IUPAC-Schreibung statt Cäsium
(56, 'Ba', 'Barium', []),
(57, 'La', 'Lanthan', []),
(58, 'Ce', 'Cer', []),
(59, 'Pr', 'Praseodym', []),
(60, 'Nd', 'Neodym', []),
(61, 'Pm', 'Promethium', []),
(62, 'Sm', 'Samarium', []),
(63, 'Eu', 'Europium', []),
(64, 'Gd', 'Gadolinium', []),
(65, 'Tb', 'Terbium', []),
(66, 'Dy', 'Dysprosium', []),
(67, 'Ho', 'Holmium', []),
(68, 'Er', 'Erbium', []),
(69, 'Tm', 'Thulium', []),
(70, 'Yb', 'Ytterbium', []),
(71, 'Lu', 'Lutetium', []),
(72, 'Hf', 'Hafnium', []),
(73, 'Ta', 'Tantal', []),
(74, 'W', 'Wolfram', []),
(75, 'Re', 'Rhenium', []),
(76, 'Os', 'Osmium', []),
(77, 'Ir', 'Iridium', []),
(78, 'Pt', 'Platin', []),
(79, 'Au', 'Gold', []),
(80, 'Hg', 'Quecksilber', []),
(81, 'Tl', 'Thallium', []),
(82, 'Pb', 'Blei', []),
(83, 'Bi', 'Bismut', [3]), # IUPAC-Schreibung statt Wismut
(84, 'Po', 'Polonium', []),
(85, 'At', 'Astat', []),
(86, 'Rn', 'Radon', []),
(87, 'Fr', 'Francium', []),
(88, 'Ra', 'Radium', []),
(89, 'Ac', 'Actinium', []),
(90, 'Th', 'Thorium', []),
(91, 'Pa', 'Protactinium', []),
(92, 'U', 'Uran', []),
(93, 'Np', 'Neptunium', []),
(94, 'Pu', 'Plutonium', []),
(95, 'Am', 'Americium', []),
(96, 'Cm', 'Curium', []),
(97, 'Bk', 'Berkelium', []),
(98, 'Cf', 'Californium', []),
(99, 'Es', 'Einsteinium', []),
(100, 'Fm', 'Fermium', []),
(101, 'Md', 'Mendelevium', []),
(102, 'No', 'Nobelium', []),
(103, 'Lr', 'Lawrencium', []),
(104, 'Rf', 'Rutherfordium', []),
(105, 'Db', 'Dubnium', []),
(106, 'Sg', 'Seaborgium', []),
(107, 'Bh', 'Bohrium', []),
(108, 'Hs', 'Hassium', []),
(109, 'Mt', 'Meitnerium', []),
(110, 'Ds', 'Darmstadtium', []),
(111, 'Rg', 'Roentgenium', []),
(112, 'Cn', 'Copernicium', []),
(113, 'Nh', 'Nihonium', []),
(114, 'Fl', 'Flerovium', []),
(115, 'Mc', 'Moscovium', []),
(116, 'Lv', 'Livermorium', []),
(117, 'Ts', 'Tennessin', [4]), # IUPAC 2016, nicht „Tenness"
(118, 'Og', 'Oganesson', []),
]
# Sanity checks
assert len(ELEMENTS) == 118, f'expected 118 elements, got {len(ELEMENTS)}'
zs = [e[0] for e in ELEMENTS]
assert zs == list(range(1, 119)), 'Z must be 1..118 sequentially'
syms = [e[1] for e in ELEMENTS]
assert len(set(syms)) == 118, f'duplicate symbols: {[s for s in syms if syms.count(s) > 1]}'
names = [e[2] for e in ELEMENTS]
assert len(set(names)) == 118, f'duplicate names: {[n for n in names if names.count(n) > 1]}'
with open(OUT, 'w', encoding='utf-8') as f:
for i, (z, sym, name, extras) in enumerate(ELEMENTS, 1):
source_refs = [1, 2] + extras
card = {
'id': f'F-{i:03d}',
'type': 'basic-reverse',
'fields': {'front': sym, 'back': name},
'source_refs': source_refs,
}
f.write(json.dumps(card, ensure_ascii=False) + '\n')
# Sampling für Reviewer-Stop
random.seed(42)
sample = random.sample(ELEMENTS, 10)
print(f'wrote {len(ELEMENTS)} elements to {OUT}')
print()
print('=== 10 zufällige Karten (Sampling) ===')
for z, sym, name, extras in sample:
refs = '[1][2]' + (' [' + ','.join(map(str, extras)) + ']' if extras else '')
print(f' Z={z:3d} {sym:3s}{name:18s} {refs}')
print()
print('=== Streitfall-Karten ===')
for z, sym, name, extras in ELEMENTS:
if extras:
print(f' Z={z:3d} {sym:3s}{name:18s} (extra ref {extras})')