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>
171 lines
5.5 KiB
Python
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})')
|