@jjlmoya/utils-civic 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +40 -0
- package/.gitignore +6 -0
- package/.stylelintrc.json +98 -0
- package/astro.config.mjs +19 -0
- package/eslint.config.js +201 -0
- package/package.json +79 -0
- package/prompts/create_tool.md +98 -0
- package/prompts/i18n/de.md +16 -0
- package/prompts/i18n/en.md +16 -0
- package/prompts/i18n/es.md +16 -0
- package/prompts/i18n/fr.md +16 -0
- package/prompts/i18n/id.md +16 -0
- package/prompts/i18n/it.md +16 -0
- package/prompts/i18n/ja.md +16 -0
- package/prompts/i18n/ko.md +16 -0
- package/prompts/i18n/nl.md +16 -0
- package/prompts/i18n/pl.md +16 -0
- package/prompts/i18n/pt.md +16 -0
- package/prompts/i18n/ru.md +16 -0
- package/prompts/i18n/sv.md +16 -0
- package/prompts/i18n/tr.md +16 -0
- package/prompts/i18n/zh.md +16 -0
- package/prompts/seo.md +58 -0
- package/prompts/translations/french.md +33 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/CivicCategorySEO.astro +9 -0
- package/src/category/i18n/de.ts +21 -0
- package/src/category/i18n/en.ts +25 -0
- package/src/category/i18n/es.ts +21 -0
- package/src/category/i18n/fr.ts +21 -0
- package/src/category/i18n/id.ts +21 -0
- package/src/category/i18n/it.ts +21 -0
- package/src/category/i18n/ja.ts +21 -0
- package/src/category/i18n/ko.ts +21 -0
- package/src/category/i18n/nl.ts +21 -0
- package/src/category/i18n/pl.ts +21 -0
- package/src/category/i18n/pt.ts +21 -0
- package/src/category/i18n/ru.ts +21 -0
- package/src/category/i18n/sv.ts +21 -0
- package/src/category/i18n/tr.ts +21 -0
- package/src/category/i18n/zh.ts +21 -0
- package/src/category/index.ts +24 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +10 -0
- package/src/entries.ts +9 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +20 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +163 -0
- package/src/pages/[locale].astro +253 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/category_seo_quality.test.ts +79 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +18 -0
- package/src/tests/i18n_coverage.test.ts +34 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +23 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_em_dash.test.ts +47 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/qa-test-helpers.ts +31 -0
- package/src/tests/qa_bibliography_links.test.ts +53 -0
- package/src/tests/qa_claim_evidence.test.ts +68 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +45 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/registry_contract.test.ts +67 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/seo_translation_completeness.test.ts +75 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/spanish_leakage.test.ts +175 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tests/tool_validation.test.ts +16 -0
- package/src/tests/translation_copy.test.ts +123 -0
- package/src/tool/election-seat-apportionment-calculator/bibliography.astro +6 -0
- package/src/tool/election-seat-apportionment-calculator/bibliography.ts +16 -0
- package/src/tool/election-seat-apportionment-calculator/component.astro +69 -0
- package/src/tool/election-seat-apportionment-calculator/controller.ts +181 -0
- package/src/tool/election-seat-apportionment-calculator/dom-views.ts +75 -0
- package/src/tool/election-seat-apportionment-calculator/election-seat-apportionment-calculator.css +585 -0
- package/src/tool/election-seat-apportionment-calculator/entry.ts +28 -0
- package/src/tool/election-seat-apportionment-calculator/evaluator.ts +20 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/de.ts +55 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/en.ts +108 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/es.ts +50 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/fr.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/id.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/it.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/ja.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/ko.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/nl.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/pl.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/pt.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/ru.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/sv.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/tr.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/i18n/zh.ts +48 -0
- package/src/tool/election-seat-apportionment-calculator/index.ts +11 -0
- package/src/tool/election-seat-apportionment-calculator/logic.test.ts +90 -0
- package/src/tool/election-seat-apportionment-calculator/logic.ts +268 -0
- package/src/tool/election-seat-apportionment-calculator/seo.astro +14 -0
- package/src/tool/election-seat-apportionment-calculator/sharing.test.ts +34 -0
- package/src/tool/election-seat-apportionment-calculator/sharing.ts +88 -0
- package/src/tool/election-seat-apportionment-calculator/storage.ts +28 -0
- package/src/tool/election-seat-apportionment-calculator/ui.ts +92 -0
- package/src/tools.ts +7 -0
- package/src/types.ts +69 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Zetelverdeling',
|
|
9
|
+
eventsTitle: 'Hier worden de volgende zetels bepaald',
|
|
10
|
+
sensitivityTitle: 'Wat er rond de drempel verandert',
|
|
11
|
+
methodLabel: 'Verdelingsmethode', dhondtLabel: 'D\'Hondt hoogste gemiddelden', sainteLagueLabel: 'Sainte-Laguë hoogste gemiddelden', hareLabel: 'Hare-quota en grootste resten', partiesLabel: 'Stemmen per partij of lijst', partyNameLabel: 'Naam van partij of lijst', votesLabel: 'Stemmen', addParty: 'Partij toevoegen', removeParty: 'Partij verwijderen', shareResult: 'Deellink kopiëren', shareCopied: 'Deellink gekopieerd.', shareCopyFallback: 'Kopieer de deellink uit het dialoogvenster.', invalidSharedResult: 'Deze gedeelde resultaatlink is ongeldig. Het voorbeeld wordt getoond.', totalSeatsLabel: 'Te verdelen zetels', thresholdLabel: 'Kiesdrempel', districtsLabel: 'Optionele indeling van districten', districtsHelp: 'Voer zetelaantallen gescheiden door komma\'s in. Het model gebruikt in elk district hetzelfde stemmenprofiel.', districtsPlaceholder: 'Voorbeeld: 10, 8, 7', reset: 'Voorbeeld herstellen', allocatedSeatsLabel: 'Verdeelde zetels', voteShareLabel: 'Stempercentage', seatShareLabel: 'Zetelpercentage', excludedLabel: 'Uitgesloten door drempel', chamberLabel: 'Zetelverdeling', quotientLabel: 'Quotiënt', remainderLabel: 'Rest', eventLabel: 'Volgorde van zeteltoekenning', eligibleVotesLabel: 'Geldige stemmen', excludedVotesLabel: 'Uitgesloten stemmen', effectiveThresholdLabel: 'Waargenomen zeteldrempel', sensitivityLabel: 'Gevoeligheid voor de drempel', lowerThresholdLabel: 'Bij een drempel die 1 punt lager is', higherThresholdLabel: 'Bij een drempel die 1 punt hoger is', changedLabel: 'Gewijzigde zetels', noChangesLabel: 'Geen zetelwijzigingen in deze test', districtAssumption: 'De districtmodus is een scenario: het ingevoerde nationale stemmenprofiel wordt in elk district herhaald en vervangt geen officiële districtuitslagen.', modelNotice: 'Alleen een educatief model. Geen officiële verkiezingsuitslag, juridisch advies of voorspelling.', emptyState: 'Voer stemmen in om de zetelverdeling te zien.', invalidNumber: 'Gebruik een geldig niet-negatief getal.', seatUnit: 'zetels',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'Is dit een officiële verkiezingscalculator?', answer: 'Nee. Het is een offline educatief model. Een echte telling kan wettelijke drempels, territoriale regels, bijzondere zetels, afrondingen en procedures bij gelijke standen bevatten die dit algemene model niet kent.' },
|
|
15
|
+
{ question: 'Wat is het verschil tussen D\'Hondt, Sainte-Laguë en Hare?', answer: 'D\'Hondt en Sainte-Laguë rangschikken opeenvolgende quotiënten. D\'Hondt deelt door 1, 2, 3 enzovoort, terwijl Sainte-Laguë oneven delers gebruikt. Hare verdeelt eerst hele quota en daarna de resterende zetels via de grootste resten.' },
|
|
16
|
+
{ question: 'Wat veronderstelt de districtoptie?', answer: 'Ze herhaalt het ingevoerde stemmenprofiel in elk genoemd district en verdeelt de zetels per district. Gebruik dit om districtsgrootte te onderzoeken, niet om lokale uitslagen te reconstrueren.' },
|
|
17
|
+
{ question: 'Kan ik een vast scenario delen?', answer: 'Ja. Kopieer de deellink nadat je het scenario hebt ingevoerd. De parameter result bewaart partijen, stemmen, zetels, methode, drempel en districten voor een andere lezer.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Stemtotalen invoeren', text: 'Voeg elke partij of lijst toe en voer het aantal stemmen in.' },
|
|
21
|
+
{ name: 'Verdelingsregel kiezen', text: 'Kies D\'Hondt, Sainte-Laguë of Hare-quota met de grootste resten.' },
|
|
22
|
+
{ name: 'Zetels en drempel instellen', text: 'Voer beschikbare zetels en een drempel in als het scenario die gebruikt.' },
|
|
23
|
+
{ name: 'De verdeling lezen', text: 'Bekijk de kamer, percentages, toekenningsvolgorde, uitsluitingen en gevoeligheid.' },
|
|
24
|
+
{ name: 'Het scenario delen', text: 'Kopieer de link zodat dezelfde invoer elders kan worden geopend.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Calculator voor de verdeling van verkiezingszetels', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'Modelleert een proportionele zetelverdeling met D\'Hondt, Sainte-Laguë en de grootste rest van Hare.', url: 'https://gamebob.dev/nl/zetelverdeling-verkiezingen-calculator', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Een proportionele zetelverdeling modelleren', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'zetelverdeling-verkiezingen-calculator', title: 'Calculator voor de verdeling van verkiezingszetels', description: 'Zet stemmen om in zetels met D\'Hondt, Sainte-Laguë of Hare. Vergelijk drempels en districten in een transparant offline scenario.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'Van stemmen naar vertegenwoordiging', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'Zetelverdeling verbindt een stemmentelling met de samenstelling van een volksvertegenwoordiging. Voer partijen of lijsten, stemmen en beschikbare zetels in. De calculator toont de kamer, aandelen en de controleerbare volgorde waarin elke zetel wordt toegekend. Het resultaat is bedoeld om scenario\'s te leren en uit te leggen, niet om gekozenen vast te stellen.' },
|
|
35
|
+
{ type: 'title', text: 'Drie methoden met dezelfde gegevens vergelijken', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'D\'Hondt en Sainte-Laguë zijn methoden van de hoogste gemiddelden. Voor elke mogelijke volgende zetel ontstaat een quotiënt en de hoogste quotiënten worden gekozen. D\'Hondt deelt door 1, 2, 3 en verder; Sainte-Laguë gebruikt 1, 3, 5 en verder. Dezelfde stemmen kunnen dus een andere vertegenwoordiging opleveren.' },
|
|
37
|
+
{ type: 'paragraph', html: 'De grootste rest van Hare begint met een quota van de geldige stemmen gedeeld door het aantal zetels. Elke lijst krijgt eerst hele quota\'s, waarna de resterende zetels naar de grootste resten gaan. Bij echte verkiezingen moet je ook de wet en de regels voor gelijke standen controleren.' },
|
|
38
|
+
{ type: 'table', headers: ['Methode', 'Verdelingsstap', 'Nuttige vraag'], rows: [['D\'Hondt', 'Rangschik stemmen gedeeld door 1, 2, 3 en verder.', 'Hoe sterk bevoordeelt dit grotere lijsten?'], ['Sainte-Laguë', 'Rangschik stemmen gedeeld door oneven delers.', 'Wat verandert door een gelijkmatiger reeks?'], ['Grootste rest van Hare', 'Geef hele quota\'s en rangschik daarna de resten.', 'Wie profiteert wanneer een rest de laatste zetel bepaalt?']] },
|
|
39
|
+
{ type: 'title', text: 'Drempels en districten als expliciete aannames testen', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'De drempel sluit partijen uit waarvan het percentage onder de ingevoerde waarde blijft voordat zetels worden verdeeld. Het aantal uitgesloten stemmen blijft zichtbaar. De waargenomen zeteldrempel is het laagste stemmenpercentage van een lijst die in deze berekening een zetel krijgt en is geen wettelijke bewering.' },
|
|
41
|
+
{ type: 'list', items: ['Begin met het officiële aantal zetels van de onderzochte kamer.', 'Gebruik alleen een drempel als de regel die je modelleert die heeft.', 'Vergelijk methoden met dezelfde stemmen en hetzelfde aantal zetels.', 'Gebruik de gevoeligheidskaarten om lijsten dicht bij de grens te vinden.'] },
|
|
42
|
+
{ type: 'tip', title: 'De districtaanname juist lezen', html: 'De districtmodus herhaalt hetzelfde nationale stemmenprofiel in elk ingevoerd district. Daarmee is het effect van districtsgrootte zichtbaar, maar niet van lokale verschuivingen, regionale partijen, bijzondere zetels, compensatiezetels of coalitieregels.' },
|
|
43
|
+
{ type: 'title', text: 'Wat dit model niet kan beslissen', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'Kiesrecht verschilt per rechtsgebied. Een echte telling kan een andere drempelbasis, een variant van Sainte-Laguë, meerdere verdelingsrondes, territoriale correcties, kandidaatregels of een willekeurige tie-break gebruiken. Controleer de bevoegde kiesautoriteit en de toepasselijke wet voordat je een echte uitslag uitlegt.' },
|
|
45
|
+
{ type: 'tip', title: 'Van verrassing naar controle', html: 'De tabellen met quotiënten en resten vormen een auditspoor. Als een zetel onverwacht is, bekijk dan het volgende verliezende quotiënt of de volgende rest en vergelijk die met de precieze regel van het rechtsgebied.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Podział mandatów',
|
|
9
|
+
eventsTitle: 'Tutaj rozstrzygają się kolejne mandaty',
|
|
10
|
+
sensitivityTitle: 'Co zmienia się przy progu',
|
|
11
|
+
methodLabel: 'Metoda podziału', dhondtLabel: 'Metoda D\'Hondta', sainteLagueLabel: 'Metoda Sainte-Laguë', hareLabel: 'Kwota Hare\'a i największe reszty', partiesLabel: 'Głosy według partii lub listy', partyNameLabel: 'Nazwa partii lub listy', votesLabel: 'Głosy', addParty: 'Dodaj partię', removeParty: 'Usuń partię', shareResult: 'Kopiuj link udostępniania', shareCopied: 'Link udostępniania skopiowany.', shareCopyFallback: 'Skopiuj link udostępniania z okna dialogowego.', invalidSharedResult: 'Ten udostępniony link jest nieprawidłowy. Wyświetlono przykład.', totalSeatsLabel: 'Mandaty do podziału', thresholdLabel: 'Próg wyborczy', districtsLabel: 'Opcjonalne grupowanie okręgów', districtsHelp: 'Wpisz liczby mandatów oddzielone przecinkami. Model stosuje ten sam profil głosów w każdym okręgu.', districtsPlaceholder: 'Przykład: 10, 8, 7', reset: 'Przywróć przykład', allocatedSeatsLabel: 'Podzielone mandaty', voteShareLabel: 'Udział głosów', seatShareLabel: 'Udział mandatów', excludedLabel: 'Wykluczona przez próg', chamberLabel: 'Podział mandatów', quotientLabel: 'Iloraz', remainderLabel: 'Reszta', eventLabel: 'Kolejność przyznawania mandatów', eligibleVotesLabel: 'Głosy uwzględnione', excludedVotesLabel: 'Głosy wykluczone', effectiveThresholdLabel: 'Zaobserwowany próg mandatu', sensitivityLabel: 'Wrażliwość na próg', lowerThresholdLabel: 'Przy progu niższym o 1 punkt', higherThresholdLabel: 'Przy progu wyższym o 1 punkt', changedLabel: 'Zmienione mandaty', noChangesLabel: 'W tym teście żaden mandat się nie zmienia', districtAssumption: 'Tryb okręgów jest scenariuszem: powtarza wpisany krajowy profil głosów w każdym okręgu i nie zastępuje oficjalnych wyników okręgowych.', modelNotice: 'Tylko model edukacyjny. To nie jest oficjalny wynik wyborów, porada prawna ani prognoza.', emptyState: 'Wpisz głosy, aby zobaczyć podział mandatów.', invalidNumber: 'Użyj prawidłowej liczby nieujemnej.', seatUnit: 'mandatów',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'Czy to oficjalny kalkulator wyborczy?', answer: 'Nie. To edukacyjny model offline. Rzeczywiste liczenie może uwzględniać progi prawne, zasady terytorialne, mandaty specjalne, zaokrąglenia i procedury rozstrzygania remisów, których ten ogólny model nie zna.' },
|
|
15
|
+
{ question: 'Czym różnią się D\'Hondt, Sainte-Laguë i Hare?', answer: 'D\'Hondt i Sainte-Laguë szeregują kolejne ilorazy. D\'Hondt dzieli przez 1, 2, 3 i dalej, a Sainte-Laguë używa dzielników nieparzystych. Hare przyznaje najpierw pełne kwoty, a pozostałe mandaty rozdziela według największych reszt.' },
|
|
16
|
+
{ question: 'Co zakłada opcja okręgów?', answer: 'Powtarza wpisany profil głosów w każdym wskazanym okręgu i dzieli mandaty osobno. Służy do badania wielkości okręgu, a nie do odtwarzania lokalnych wyników.' },
|
|
17
|
+
{ question: 'Czy mogę udostępnić stały scenariusz?', answer: 'Tak. Skopiuj link po wpisaniu scenariusza. Parametr result zapisuje partie, głosy, mandaty, metodę, próg i okręgi, aby inny czytelnik otworzył ten sam model.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Wpisz głosy', text: 'Dodaj każdą partię lub listę i wpisz jej liczbę głosów.' },
|
|
21
|
+
{ name: 'Wybierz regułę podziału', text: 'Wybierz D\'Hondta, Sainte-Laguë albo kwotę Hare\'a z największymi resztami.' },
|
|
22
|
+
{ name: 'Ustaw mandaty i próg', text: 'Podaj dostępne mandaty oraz próg, jeśli scenariusz go używa.' },
|
|
23
|
+
{ name: 'Odczytaj podział', text: 'Sprawdź skład izby, udziały, kolejność przyznawania, wykluczenia i wrażliwość.' },
|
|
24
|
+
{ name: 'Udostępnij scenariusz', text: 'Skopiuj link, aby zachować dokładnie te same dane dla innego czytelnika.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Kalkulator podziału mandatów wyborczych', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'Modeluje proporcjonalny podział mandatów metodami D\'Hondta, Sainte-Laguë i największej reszty Hare\'a.', url: 'https://gamebob.dev/pl/kalkulator-podzialu-mandatow-wyborczych', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Modelowanie proporcjonalnego podziału mandatów', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'kalkulator-podzialu-mandatow-wyborczych', title: 'Kalkulator podziału mandatów wyborczych', description: 'Przelicz głosy na mandaty metodą D\'Hondta, Sainte-Laguë lub największej reszty Hare\'a. Porównuj progi i okręgi w przejrzystym scenariuszu offline.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'Od głosów do reprezentacji', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'Podział mandatów łączy liczbę głosów ze składem organu przedstawicielskiego. Wpisz partie lub listy, głosy oraz liczbę dostępnych mandatów. Kalkulator pokazuje skład izby, udziały oraz możliwą do prześledzenia kolejność przyznawania mandatów. To model do nauki i wyjaśniania scenariuszy, a nie ustalenie, kto został wybrany.' },
|
|
35
|
+
{ type: 'title', text: 'Porównaj trzy metody na tych samych danych', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'D\'Hondt i Sainte-Laguë to metody najwyższych średnich. Każdy możliwy kolejny mandat tworzy iloraz, a wybierane są najwyższe wartości. D\'Hondt dzieli przez 1, 2, 3 i kolejne liczby, natomiast Sainte-Laguë przez 1, 3, 5 i kolejne liczby nieparzyste. Te same głosy mogą więc dać inny skład.' },
|
|
37
|
+
{ type: 'paragraph', html: 'Największa reszta Hare\'a zaczyna od kwoty równej głosom uwzględnionym podzielonym przez liczbę mandatów. Każda lista otrzymuje najpierw pełne kwoty, a pozostałe mandaty trafiają do największych reszt. W prawdziwych wyborach trzeba dodatkowo sprawdzić próg ustawowy i reguły remisów.' },
|
|
38
|
+
{ type: 'table', headers: ['Metoda', 'Etap podziału', 'Przydatne pytanie'], rows: [['D\'Hondt', 'Uszereguj głosy podzielone przez 1, 2, 3 i dalej.', 'Jak metoda pomaga większym listom?'], ['Sainte-Laguë', 'Uszereguj głosy podzielone przez dzielniki nieparzyste.', 'Co zmienia bardziej równomierny ciąg dzielników?'], ['Największa reszta Hare\'a', 'Przyznaj pełne kwoty, a potem uszereguj reszty.', 'Kto zyskuje, gdy reszta rozstrzyga ostatni mandat?']] },
|
|
39
|
+
{ type: 'title', text: 'Testuj progi i okręgi jako jawne założenia', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'Próg wyklucza przed podziałem partie, których udział jest niższy od wpisanej wartości. Liczba wykluczonych głosów pozostaje widoczna. Zaobserwowany próg mandatu to najniższy udział głosów listy, która otrzymuje mandat w tym obliczeniu, a nie twierdzenie o prawie wyborczym.' },
|
|
41
|
+
{ type: 'list', items: ['Zacznij od oficjalnej liczby mandatów badanej izby.', 'Wpisuj próg tylko wtedy, gdy przewiduje go analizowana reguła.', 'Porównuj metody przy tych samych głosach i mandatach.', 'Korzystaj z kart wrażliwości, aby znaleźć listy blisko granicy.'] },
|
|
42
|
+
{ type: 'tip', title: 'Dobrze interpretuj tryb okręgów', html: 'Tryb okręgów powtarza ten sam krajowy profil głosów w każdym wpisanym okręgu. Pokazuje wpływ wielkości okręgu, ale nie odtwarza lokalnych zmian, partii regionalnych, mandatów specjalnych, mandatów wyrównawczych ani reguł koalicyjnych.' },
|
|
43
|
+
{ type: 'title', text: 'Czego ten model nie rozstrzyga', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'Prawo wyborcze zależy od jurysdykcji. Prawdziwe liczenie może używać innej podstawy progu, odmiany Sainte-Laguë, kilku etapów podziału, korekt terytorialnych, reguł kandydackich lub losowego rozstrzygnięcia remisu. Przed wyjaśnieniem realnego wyniku sprawdź właściwy organ wyborczy i obowiązujące przepisy.' },
|
|
45
|
+
{ type: 'tip', title: 'Zamień zaskoczenie w kontrolę', html: 'Tabele ilorazów i reszt tworzą ślad audytowy. Jeśli mandat wydaje się zaskakujący, sprawdź kolejny przegrany iloraz lub resztę i porównaj je z dokładną regułą danej jurysdykcji.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Distribuição de assentos',
|
|
9
|
+
eventsTitle: 'Aqui são definidos os próximos assentos',
|
|
10
|
+
sensitivityTitle: 'O que muda perto do limiar',
|
|
11
|
+
methodLabel: 'Método de distribuição', dhondtLabel: 'Maiores médias de D\'Hondt', sainteLagueLabel: 'Maiores médias de Sainte-Laguë', hareLabel: 'Quociente Hare e maiores restos', partiesLabel: 'Votos por partido ou lista', partyNameLabel: 'Nome do partido ou lista', votesLabel: 'Votos', addParty: 'Adicionar partido', removeParty: 'Remover partido', shareResult: 'Copiar ligação para partilhar', shareCopied: 'Ligação copiada.', shareCopyFallback: 'Copie a ligação na janela de diálogo.', invalidSharedResult: 'Esta ligação partilhada é inválida. O exemplo foi carregado.', totalSeatsLabel: 'Lugares a distribuir', thresholdLabel: 'Limiar de elegibilidade', districtsLabel: 'Agrupamento opcional de círculos', districtsHelp: 'Introduza os números de lugares separados por vírgulas. O modelo aplica o mesmo perfil de votos em cada círculo.', districtsPlaceholder: 'Exemplo: 10, 8, 7', reset: 'Repor exemplo', allocatedSeatsLabel: 'Lugares distribuídos', voteShareLabel: 'Percentagem de votos', seatShareLabel: 'Percentagem de lugares', excludedLabel: 'Excluído pelo limiar', chamberLabel: 'Distribuição de lugares', quotientLabel: 'Quociente', remainderLabel: 'Resto', eventLabel: 'Ordem de atribuição dos lugares', eligibleVotesLabel: 'Votos elegíveis', excludedVotesLabel: 'Votos excluídos', effectiveThresholdLabel: 'Limiar observado para obter lugar', sensitivityLabel: 'Sensibilidade ao limiar', lowerThresholdLabel: 'Com limiar um ponto abaixo', higherThresholdLabel: 'Com limiar um ponto acima', changedLabel: 'Lugares alterados', noChangesLabel: 'Nenhum lugar muda neste teste', districtAssumption: 'O modo por círculos é um cenário: repete o perfil nacional de votos introduzido em cada círculo e não substitui resultados oficiais locais.', modelNotice: 'Apenas um modelo educativo. Não é um resultado eleitoral oficial, aconselhamento jurídico ou previsão.', emptyState: 'Introduza votos para ver a distribuição.', invalidNumber: 'Use um número válido não negativo.', seatUnit: 'lugares',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'É uma calculadora eleitoral oficial?', answer: 'Não. É um modelo educativo offline. Uma contagem real pode incluir limiares legais, regras territoriais, lugares reservados, arredondamentos e procedimentos de desempate que este modelo geral não conhece.' },
|
|
15
|
+
{ question: 'Qual é a diferença entre D\'Hondt, Sainte-Laguë e Hare?', answer: 'D\'Hondt e Sainte-Laguë ordenam quocientes sucessivos. D\'Hondt divide por 1, 2, 3 e seguintes, enquanto Sainte-Laguë usa divisores ímpares. Hare atribui primeiro os quocientes inteiros e depois os lugares restantes pelos maiores restos.' },
|
|
16
|
+
{ question: 'O que pressupõe a opção de círculos?', answer: 'Repete o perfil de votos introduzido em cada círculo indicado e distribui os lugares separadamente. Serve para estudar a dimensão do círculo, não para reconstruir resultados locais.' },
|
|
17
|
+
{ question: 'Posso partilhar um cenário fixo?', answer: 'Sim. Copie a ligação depois de introduzir o cenário. O parâmetro result guarda partidos, votos, lugares, método, limiar e círculos para que outra pessoa abra o mesmo modelo.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Introduzir os votos', text: 'Adicione cada partido ou lista e introduza o respetivo número de votos.' },
|
|
21
|
+
{ name: 'Escolher a regra', text: 'Selecione D\'Hondt, Sainte-Laguë ou quociente Hare e maiores restos.' },
|
|
22
|
+
{ name: 'Definir lugares e limiar', text: 'Indique os lugares disponíveis e um limiar se o cenário o utilizar.' },
|
|
23
|
+
{ name: 'Ler a distribuição', text: 'Consulte a câmara, as percentagens, a ordem, as exclusões e a sensibilidade.' },
|
|
24
|
+
{ name: 'Partilhar o cenário', text: 'Copie a ligação para conservar exatamente os mesmos dados para outro leitor.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Calculadora de distribuição de lugares eleitorais', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'Modela a distribuição proporcional de lugares com D\'Hondt, Sainte-Laguë e maior resto de Hare.', url: 'https://gamebob.dev/pt/calculadora-distribuicao-assentos-eleitorais', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Modelar uma distribuição proporcional', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'calculadora-distribuicao-assentos-eleitorais', title: 'Calculadora de distribuição de lugares eleitorais', description: 'Converta votos em lugares com D\'Hondt, Sainte-Laguë ou maior resto de Hare. Compare limiares e círculos num cenário offline transparente.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'Dos votos à representação', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'A distribuição de lugares liga a contagem de votos à composição de uma assembleia. Introduza partidos ou listas, os seus votos e o número de lugares disponíveis. A calculadora mostra a câmara, as percentagens e a ordem verificável em que cada lugar é atribuído. É um modelo para aprender e explicar cenários, não uma declaração de quem foi eleito.' },
|
|
35
|
+
{ type: 'title', text: 'Compare três métodos com os mesmos dados', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'D\'Hondt e Sainte-Laguë são métodos de maiores médias. Cada lugar possível gera um quociente e os mais altos são escolhidos. D\'Hondt divide por 1, 2, 3 e seguintes; Sainte-Laguë usa 1, 3, 5 e seguintes. Os mesmos votos podem, por isso, produzir uma representação diferente.' },
|
|
37
|
+
{ type: 'paragraph', html: 'O maior resto de Hare começa com um quociente igual aos votos elegíveis divididos pelo número de lugares. Cada lista recebe primeiro os quocientes inteiros e os lugares restantes vão para os maiores restos. Numa eleição real também contam o limiar legal e as regras para empates.' },
|
|
38
|
+
{ type: 'table', headers: ['Método', 'Etapa de distribuição', 'Pergunta útil'], rows: [['D\'Hondt', 'Ordenar os votos divididos por 1, 2, 3 e seguintes.', 'Como favorece as listas maiores?'], ['Sainte-Laguë', 'Ordenar os votos divididos por divisores ímpares.', 'O que muda com uma série mais equilibrada?'], ['Maior resto de Hare', 'Atribuir quocientes inteiros e depois ordenar os restos.', 'Quem beneficia quando um resto decide o último lugar?']] },
|
|
39
|
+
{ type: 'title', text: 'Testar limiares e círculos como pressupostos explícitos', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'O limiar exclui antes da distribuição os partidos cuja percentagem fica abaixo do valor introduzido. Os votos excluídos continuam visíveis. O limiar observado para obter lugar é a percentagem mais baixa de uma lista que recebe um lugar neste cálculo e não uma afirmação jurídica.' },
|
|
41
|
+
{ type: 'list', items: ['Comece pelo número oficial de lugares da assembleia estudada.', 'Use um limiar apenas quando a regra eleitoral analisada o tiver.', 'Compare métodos mantendo iguais os votos e os lugares.', 'Use as tarjetas de sensibilidade para localizar listas perto do limite.'] },
|
|
42
|
+
{ type: 'tip', title: 'Interpretar o modo por círculos', html: 'O modo por círculos repete o mesmo perfil nacional de votos em cada círculo indicado. Isola o efeito da dimensão do círculo, mas não representa alterações locais, partidos regionais, lugares reservados, lugares de compensação ou regras de coligação.' },
|
|
43
|
+
{ type: 'title', text: 'O que este modelo não pode decidir', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'A lei eleitoral depende da jurisdição. Uma contagem real pode usar outra base de limiar, uma variante de Sainte-Laguë, várias fases de distribuição, correções territoriais, regras para candidatos ou um desempate aleatório. Consulte a autoridade eleitoral e a legislação aplicável antes de explicar um resultado real.' },
|
|
45
|
+
{ type: 'tip', title: 'Transformar uma surpresa numa verificação', html: 'As tabelas de quocientes e restos formam uma pista de auditoria. Se um lugar surpreender, verifique o quociente ou resto seguinte que perdeu e compare-o com a regra exata da jurisdição.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Распределение мест',
|
|
9
|
+
eventsTitle: 'Здесь определяются следующие места',
|
|
10
|
+
sensitivityTitle: 'Что меняется около порога',
|
|
11
|
+
methodLabel: 'Метод распределения', dhondtLabel: 'Метод Д\'Ондта', sainteLagueLabel: 'Метод Сент-Лагю', hareLabel: 'Квота Хэра и наибольшие остатки', partiesLabel: 'Голоса партий или списков', partyNameLabel: 'Название партии или списка', votesLabel: 'Голоса', addParty: 'Добавить партию', removeParty: 'Удалить партию', shareResult: 'Скопировать ссылку', shareCopied: 'Ссылка скопирована.', shareCopyFallback: 'Скопируйте ссылку из диалогового окна.', invalidSharedResult: 'Эта ссылка на общий результат недействительна. Показан пример.', totalSeatsLabel: 'Мандаты для распределения', thresholdLabel: 'Избирательный порог', districtsLabel: 'Необязательная группировка округов', districtsHelp: 'Введите числа мандатов через запятую. Модель использует один и тот же профиль голосов в каждом округе.', districtsPlaceholder: 'Пример: 10, 8, 7', reset: 'Восстановить пример', allocatedSeatsLabel: 'Распределённые мандаты', voteShareLabel: 'Доля голосов', seatShareLabel: 'Доля мандатов', excludedLabel: 'Исключена по порогу', chamberLabel: 'Распределение мандатов', quotientLabel: 'Частное', remainderLabel: 'Остаток', eventLabel: 'Порядок получения мандатов', eligibleVotesLabel: 'Учтённые голоса', excludedVotesLabel: 'Исключённые голоса', effectiveThresholdLabel: 'Наблюдаемый порог мандата', sensitivityLabel: 'Чувствительность к порогу', lowerThresholdLabel: 'При пороге на 1 пункт ниже', higherThresholdLabel: 'При пороге на 1 пункт выше', changedLabel: 'Изменившиеся мандаты', noChangesLabel: 'В этом тесте мандаты не меняются', districtAssumption: 'Режим округов является сценарием: введённый национальный профиль голосов повторяется в каждом округе и не заменяет официальные результаты округов.', modelNotice: 'Только образовательная модель. Это не официальный результат выборов, юридическая консультация или прогноз.', emptyState: 'Введите голоса, чтобы увидеть распределение мандатов.', invalidNumber: 'Введите допустимое неотрицательное число.', seatUnit: 'мандатов',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'Это официальный калькулятор выборов?', answer: 'Нет. Это образовательная офлайн-модель. Реальный подсчёт может учитывать законные пороги, территориальные правила, специальные мандаты, округления и процедуры разрешения равенства, которых здесь нет.' },
|
|
15
|
+
{ question: 'Чем отличаются Д\'Ондт, Сент-Лагю и Хэр?', answer: 'Д\'Ондт и Сент-Лагю упорядочивают последовательные частные. Д\'Ондт делит на 1, 2, 3 и далее, а Сент-Лагю использует нечётные делители. Метод Хэра сначала выдаёт целые квоты, а оставшиеся мандаты распределяет по наибольшим остаткам.' },
|
|
16
|
+
{ question: 'Что предполагает настройка округов?', answer: 'Она повторяет введённый профиль голосов в каждом указанном округе и распределяет мандаты отдельно. Это помогает изучить размер округа, но не восстанавливает местные результаты.' },
|
|
17
|
+
{ question: 'Можно ли поделиться фиксированным сценарием?', answer: 'Да. Скопируйте ссылку после ввода сценария. Параметр result сохраняет партии, голоса, мандаты, метод, порог и округа, чтобы другой читатель открыл ту же модель.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Введите голоса', text: 'Добавьте каждую партию или список и укажите число голосов.' },
|
|
21
|
+
{ name: 'Выберите правило', text: 'Выберите Д\'Ондта, Сент-Лагю или квоту Хэра с наибольшими остатками.' },
|
|
22
|
+
{ name: 'Задайте мандаты и порог', text: 'Укажите доступные мандаты и порог, если он нужен сценарию.' },
|
|
23
|
+
{ name: 'Прочитайте распределение', text: 'Проверьте состав, доли, порядок выдачи, исключения и чувствительность.' },
|
|
24
|
+
{ name: 'Поделитесь сценарием', text: 'Скопируйте ссылку, чтобы передать другому читателю те же входные данные.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Калькулятор распределения мест на выборах', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'Моделирует пропорциональное распределение мандатов методами Д\'Ондта, Сент-Лагю и наибольшего остатка Хэра.', url: 'https://gamebob.dev/ru/kalkulyator-raspredeleniya-mest-na-vyborah', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Смоделировать пропорциональное распределение мандатов', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'kalkulyator-raspredeleniya-mest-na-vyborah', title: 'Калькулятор распределения мест на выборах', description: 'Переведите голоса в мандаты методами Д\'Ондта, Сент-Лагю или наибольшего остатка Хэра. Сравнивайте пороги и округа в прозрачном офлайн-сценарии.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'От голосов к представительству', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'Распределение мандатов связывает подсчёт голосов с составом представительного органа. Введите партии или списки, их голоса и число доступных мандатов. Калькулятор показывает состав, доли и проверяемый порядок, в котором выдаётся каждый мандат. Это модель для обучения и объяснения сценариев, а не утверждение о победителях.' },
|
|
35
|
+
{ type: 'title', text: 'Сравните три метода на одних данных', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'Д\'Ондт и Сент-Лагю относятся к методам наибольших средних. Для каждого следующего мандата вычисляется частное, и выбираются наибольшие значения. Д\'Ондт использует делители 1, 2, 3 и далее, а Сент-Лагю 1, 3, 5 и далее. Поэтому одинаковые голоса могут дать разный состав.' },
|
|
37
|
+
{ type: 'paragraph', html: 'Метод наибольшего остатка Хэра начинает с квоты: учтённые голоса делятся на число мандатов. Сначала списки получают целые квоты, затем оставшиеся мандаты переходят к наибольшим остаткам. Для реальных выборов дополнительно нужно проверить закон и правила равенства.' },
|
|
38
|
+
{ type: 'table', headers: ['Метод', 'Шаг распределения', 'Полезный вопрос'], rows: [['Д\'Ондт', 'Упорядочить голоса, разделённые на 1, 2, 3 и далее.', 'Насколько метод помогает крупным спискам?'], ['Сент-Лагю', 'Упорядочить голоса, разделённые на нечётные делители.', 'Что меняет более равномерный ряд делителей?'], ['Наибольший остаток Хэра', 'Выдать целые квоты, затем упорядочить остатки.', 'Кому помогает остаток, определяющий последний мандат?']] },
|
|
39
|
+
{ type: 'title', text: 'Проверяйте пороги и округа как явные допущения', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'Порог исключает до распределения партии, чья доля ниже введённого значения. Число исключённых голосов остаётся видимым. Наблюдаемый порог мандата является минимальной долей списка, получившего мандат в этом расчёте, а не утверждением о законе.' },
|
|
41
|
+
{ type: 'list', items: ['Начните с официального числа мандатов изучаемого органа.', 'Используйте порог только при наличии такого правила в рассматриваемой системе.', 'Сравнивайте методы с одинаковыми голосами и числом мандатов.', 'Смотрите карточки чувствительности, чтобы найти списки рядом с границей.'] },
|
|
42
|
+
{ type: 'tip', title: 'Правильно читайте режим округов', html: 'Режим округов повторяет один и тот же национальный профиль голосов в каждом введённом округе. Он показывает влияние размера округа, но не моделирует местные колебания, региональные партии, специальные и компенсационные мандаты или коалиционные правила.' },
|
|
43
|
+
{ type: 'title', text: 'Что модель не может определить', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'Избирательное право зависит от юрисдикции. В реальном подсчёте могут использоваться другая база порога, вариант Сент-Лагю, несколько этапов распределения, территориальные поправки, правила для кандидатов или случайное разрешение равенства. Перед объяснением реального результата проверьте орган выборов и действующий закон.' },
|
|
45
|
+
{ type: 'tip', title: 'Превратите неожиданность в проверку', html: 'Таблицы частных и остатков образуют след проверки. Если мандат кажется неожиданным, посмотрите следующий проигравший результат и сравните его с точным правилом нужной юрисдикции.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Mandatfördelning',
|
|
9
|
+
eventsTitle: 'Nästa mandat avgörs här',
|
|
10
|
+
sensitivityTitle: 'Vad som ändras runt tröskeln',
|
|
11
|
+
methodLabel: 'Fördelningsmetod', dhondtLabel: 'D\'Hondts högsta medeltal', sainteLagueLabel: 'Sainte-Laguës högsta medeltal', hareLabel: 'Hares kvot och största rester', partiesLabel: 'Röster per parti eller lista', partyNameLabel: 'Partiets eller listans namn', votesLabel: 'Röster', addParty: 'Lägg till parti', removeParty: 'Ta bort parti', shareResult: 'Kopiera delningslänk', shareCopied: 'Delningslänken har kopierats.', shareCopyFallback: 'Kopiera delningslänken från dialogrutan.', invalidSharedResult: 'Den delade resultatlänken är ogiltig. Exemplet visas.', totalSeatsLabel: 'Mandat att fördela', thresholdLabel: 'Valspärr', districtsLabel: 'Valfri indelning i valdistrikt', districtsHelp: 'Ange mandattal separerade med kommatecken. Modellen använder samma röstprofil i varje valdistrikt.', districtsPlaceholder: 'Exempel: 10, 8, 7', reset: 'Återställ exempel', allocatedSeatsLabel: 'Fördelade mandat', voteShareLabel: 'Röst andel', seatShareLabel: 'Mandatandel', excludedLabel: 'Utesluten av spärren', chamberLabel: 'Mandatfördelning', quotientLabel: 'Kvot', remainderLabel: 'Rest', eventLabel: 'Ordning för mandatfördelning', eligibleVotesLabel: 'Röster som räknas', excludedVotesLabel: 'Uteslutna röster', effectiveThresholdLabel: 'Observerad mandatspärr', sensitivityLabel: 'Känslighet för spärren', lowerThresholdLabel: 'Vid en spärr som är 1 punkt lägre', higherThresholdLabel: 'Vid en spärr som är 1 punkt högre', changedLabel: 'Mandat som ändras', noChangesLabel: 'Inga mandat ändras i detta test', districtAssumption: 'Distriktsläget är ett scenario: den angivna nationella röstprofilen upprepas i varje valdistrikt och ersätter inte officiella distriktsresultat.', modelNotice: 'Endast en pedagogisk modell. Inte ett officiellt valresultat, juridiskt råd eller en prognos.', emptyState: 'Ange röster för att visa mandatfördelningen.', invalidNumber: 'Använd ett giltigt icke-negativt tal.', seatUnit: 'mandat',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'Är detta en officiell valkalkylator?', answer: 'Nej. Det är en pedagogisk offlinemodell. En verklig räkning kan använda lagstadgade spärrar, territoriella regler, reserverade mandat, avrundningar och regler vid lika resultat som modellen inte känner till.' },
|
|
15
|
+
{ question: 'Vad är skillnaden mellan D\'Hondt, Sainte-Laguë och Hare?', answer: 'D\'Hondt och Sainte-Laguë rangordnar successiva kvoter. D\'Hondt delar med 1, 2, 3 och så vidare, medan Sainte-Laguë använder udda delare. Hare ger först hela kvoter och delar sedan återstående mandat efter största rest.' },
|
|
16
|
+
{ question: 'Vad antar distriktstillvalet?', answer: 'Det upprepar den angivna röstprofilen i varje distrikt och fördelar mandaten separat. Använd det för att studera distriktets storlek, inte för att återskapa lokala resultat.' },
|
|
17
|
+
{ question: 'Kan jag dela ett fast scenario?', answer: 'Ja. Kopiera delningslänken efter att scenariot har fyllts i. Parametern result sparar partier, röster, mandat, metod, spärr och distrikt så att en annan läsare öppnar samma modell.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Ange rösterna', text: 'Lägg till varje parti eller lista och ange dess röstetal.' },
|
|
21
|
+
{ name: 'Välj fördelningsregel', text: 'Välj D\'Hondt, Sainte-Laguë eller Hare med största rest.' },
|
|
22
|
+
{ name: 'Ställ in mandat och spärr', text: 'Ange tillgängliga mandat och en spärr om scenariot använder en sådan.' },
|
|
23
|
+
{ name: 'Läs fördelningen', text: 'Granska kammaren, andelarna, ordningen, uteslutningarna och känsligheten.' },
|
|
24
|
+
{ name: 'Dela scenariot', text: 'Kopiera länken för att bevara exakt samma indata för en annan läsare.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Kalkylator för fördelning av valmandat', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'Modellerar proportionell mandatfördelning med D\'Hondt, Sainte-Laguë och Hares största rest.', url: 'https://gamebob.dev/sv/kalkylator-fordelning-valmandat', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Modellera en proportionell mandatfördelning', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'kalkylator-fordelning-valmandat', title: 'Kalkylator för fördelning av valmandat', description: 'Förvandla röster till mandat med D\'Hondt, Sainte-Laguë eller Hares största rest. Jämför spärrar och distrikt i ett transparent offlinescenario.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'Från röster till representation', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'Mandatfördelningen kopplar samman rösträkningen med sammansättningen i en representativ församling. Ange partier eller listor, röster och tillgängliga mandat. Kalkylatorn visar kammaren, andelarna och den granskningsbara ordningen där varje mandat tilldelas. Resultatet är en modell för lärande och förklaring, inte ett fastställande av valda personer.' },
|
|
35
|
+
{ type: 'title', text: 'Jämför tre metoder med samma data', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'D\'Hondt och Sainte-Laguë är metoder med högsta medeltal. Varje möjligt nästa mandat ger en kvot och de högsta kvoterna väljs. D\'Hondt delar med 1, 2, 3 och vidare, medan Sainte-Laguë använder 1, 3, 5 och vidare. Samma röster kan därför ge en annan representation.' },
|
|
37
|
+
{ type: 'paragraph', html: 'Hares största rest börjar med en kvot som motsvarar de räknade rösterna delat med antalet mandat. Varje lista får först hela kvoter och återstående mandat går sedan till de största resterna. Vid riktiga val måste även lagens spärrar och regler för lika resultat kontrolleras.' },
|
|
38
|
+
{ type: 'table', headers: ['Metod', 'Fördelningssteg', 'Användbar fråga'], rows: [['D\'Hondt', 'Rangordna röster delade med 1, 2, 3 och vidare.', 'Hur gynnar metoden större listor?'], ['Sainte-Laguë', 'Rangordna röster delade med udda delare.', 'Vad ändras med en jämnare delarsekvens?'], ['Hares största rest', 'Ge hela kvoter och rangordna sedan resterna.', 'Vem gynnas när en rest avgör det sista mandatet?']] },
|
|
39
|
+
{ type: 'title', text: 'Testa spärrar och distrikt som tydliga antaganden', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'Spärren tar bort partier vars andel ligger under det angivna värdet innan mandaten fördelas. Antalet uteslutna röster förblir synligt. Den observerade mandatspärren är den lägsta röstandelen hos en lista som får mandat i just denna beräkning, inte ett påstående om lagen.' },
|
|
41
|
+
{ type: 'list', items: ['Börja med det officiella antalet mandat i den församling du studerar.', 'Använd en spärr endast när den finns i regeln du modellerar.', 'Jämför metoder med samma röster och samma antal mandat.', 'Använd känslighetskorten för att hitta listor nära gränsen.'] },
|
|
42
|
+
{ type: 'tip', title: 'Tolka distriktsscenariot rätt', html: 'Distriktsläget upprepar samma nationella röstprofil i varje angivet distrikt. Det isolerar effekten av distriktets storlek, men visar inte lokala förändringar, regionala partier, reserverade mandat, utjämningsmandat eller koalitionsregler.' },
|
|
43
|
+
{ type: 'title', text: 'Vad modellen inte kan avgöra', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'Vallagen är olika i olika jurisdiktioner. En verklig räkning kan använda en annan tröskelbas, en variant av Sainte-Laguë, flera fördelningssteg, territoriella korrigeringar, kandidatregler eller slumpmässig skiljning. Kontrollera alltid valmyndigheten och den aktuella lagen innan du förklarar ett verkligt resultat.' },
|
|
45
|
+
{ type: 'tip', title: 'Gör en överraskning till en kontroll', html: 'Tabellerna med kvoter och rester bildar ett granskningsspår. Om ett mandat överraskar, titta på nästa förlorande kvot eller rest och jämför med den exakta regeln i jurisdiktionen.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: 'Sandalye dağılımı',
|
|
9
|
+
eventsTitle: 'Sonraki sandalyeler burada belirlenir',
|
|
10
|
+
sensitivityTitle: 'Eşik çevresinde ne değişir',
|
|
11
|
+
methodLabel: 'Dağıtım yöntemi', dhondtLabel: 'D\'Hondt en yüksek ortalamalar', sainteLagueLabel: 'Sainte-Laguë en yüksek ortalamalar', hareLabel: 'Hare kotası ve en büyük kalan', partiesLabel: 'Parti veya liste bazında oylar', partyNameLabel: 'Parti veya liste adı', votesLabel: 'Oylar', addParty: 'Parti ekle', removeParty: 'Partiyi kaldır', shareResult: 'Paylaşım bağlantısını kopyala', shareCopied: 'Paylaşım bağlantısı kopyalandı.', shareCopyFallback: 'Paylaşım bağlantısını iletişim kutusundan kopyalayın.', invalidSharedResult: 'Paylaşılan sonuç bağlantısı geçersiz. Örnek gösteriliyor.', totalSeatsLabel: 'Dağıtılacak sandalye', thresholdLabel: 'Uygunluk barajı', districtsLabel: 'İsteğe bağlı bölge gruplaması', districtsHelp: 'Sandalye sayılarını virgülle ayırarak girin. Model her bölgede aynı oy profilini uygular.', districtsPlaceholder: 'Örnek: 10, 8, 7', reset: 'Örneği sıfırla', allocatedSeatsLabel: 'Dağıtılan sandalye', voteShareLabel: 'Oy payı', seatShareLabel: 'Sandalye payı', excludedLabel: 'Baraj nedeniyle dışlandı', chamberLabel: 'Sandalye dağılımı', quotientLabel: 'Bölüm', remainderLabel: 'Kalan', eventLabel: 'Sandalye verme sırası', eligibleVotesLabel: 'Geçerli oylar', excludedVotesLabel: 'Dışlanan oylar', effectiveThresholdLabel: 'Gözlenen sandalye barajı', sensitivityLabel: 'Baraj duyarlılığı', lowerThresholdLabel: 'Baraj 1 puan düşükken', higherThresholdLabel: 'Baraj 1 puan yüksekken', changedLabel: 'Değişen sandalyeler', noChangesLabel: 'Bu testte sandalye değişikliği yok', districtAssumption: 'Bölge modu bir senaryodur: girilen ulusal oy profilini her bölgede tekrarlar ve resmî bölge sonuçlarının yerini tutmaz.', modelNotice: 'Yalnızca eğitim amaçlı model. Resmî seçim sonucu, hukuki danışmanlık veya tahmin değildir.', emptyState: 'Sandalye dağılımını görmek için oyları girin.', invalidNumber: 'Geçerli, negatif olmayan bir sayı kullanın.', seatUnit: 'sandalye',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: 'Bu resmî bir seçim hesaplayıcısı mı?', answer: 'Hayır. Çevrimdışı bir eğitim modelidir. Gerçek sayımda yasal barajlar, bölgesel kurallar, ayrılmış sandalyeler, yuvarlama ve eşitlik çözüm prosedürleri bulunabilir.' },
|
|
15
|
+
{ question: 'D\'Hondt, Sainte-Laguë ve Hare arasındaki fark nedir?', answer: 'D\'Hondt ve Sainte-Laguë ardışık bölümleri sıralar. D\'Hondt 1, 2, 3 ve devamına böler, Sainte-Laguë tek sayılı bölenleri kullanır. Hare önce tam kotaları verir, kalan sandalyeleri en büyük kalanlara göre dağıtır.' },
|
|
16
|
+
{ question: 'Bölge seçeneği ne varsayar?', answer: 'Girilen oy profilini her bölgede tekrarlar ve o bölgenin sandalyelerini ayrı dağıtır. Bölge büyüklüğünü incelemek içindir; yerel sonuçları yeniden oluşturmaz.' },
|
|
17
|
+
{ question: 'Sabit bir senaryoyu paylaşabilir miyim?', answer: 'Evet. Senaryoyu girdikten sonra paylaşım bağlantısını kopyalayın. result parametresi parti, oy, sandalye, yöntem, baraj ve bölgeleri saklar.' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: 'Oyları girin', text: 'Her parti veya listeyi ekleyip oy sayısını yazın.' },
|
|
21
|
+
{ name: 'Dağıtım kuralını seçin', text: 'D\'Hondt, Sainte-Laguë veya Hare en büyük kalan yöntemini seçin.' },
|
|
22
|
+
{ name: 'Sandalye ve barajı ayarlayın', text: 'Kullanılabilir sandalyeleri ve senaryoda varsa uygunluk barajını girin.' },
|
|
23
|
+
{ name: 'Dağılımı okuyun', text: 'Meclisi, payları, verme sırasını, dışlamaları ve duyarlılığı inceleyin.' },
|
|
24
|
+
{ name: 'Senaryoyu paylaşın', text: 'Aynı girdileri başka bir okuyucuya aktarmak için bağlantıyı kopyalayın.' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Seçim sandalye dağılımı hesaplayıcısı', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: 'D\'Hondt, Sainte-Laguë ve Hare en büyük kalan yöntemleriyle orantılı sandalye dağılımını modeller.', url: 'https://gamebob.dev/tr/secim-sandalye-dagilimi-hesaplayici', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: 'Orantılı sandalye dağılımını modelleme', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'secim-sandalye-dagilimi-hesaplayici', title: 'Seçim sandalye dağılımı hesaplayıcısı', description: 'Oyları D\'Hondt, Sainte-Laguë veya Hare en büyük kalan yöntemiyle sandalyelere dönüştürün. Barajları ve bölgeleri şeffaf bir çevrimdışı senaryoda karşılaştırın.', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: 'Oy toplamlarından temsile', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: 'Sandalye dağılımı, oy sayımı ile temsil organının yapısını birbirine bağlar. Parti veya listeleri, oyları ve kullanılabilir sandalye sayısını girin. Hesaplayıcı meclisi, oranları ve her sandalyenin verildiği izlenebilir sırayı gösterir. Bu, senaryoları öğrenmek ve açıklamak için bir modeldir; seçilen kişileri resmen belirlemez.' },
|
|
35
|
+
{ type: 'title', text: 'Aynı verilerle üç yöntemi karşılaştırın', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: 'D\'Hondt ve Sainte-Laguë en yüksek ortalama yöntemleridir. Olası her sonraki sandalye için bir bölüm hesaplanır ve en yüksek bölümler seçilir. D\'Hondt 1, 2, 3 ve devamını, Sainte-Laguë ise 1, 3, 5 ve devamındaki tek bölenleri kullanır. Bu nedenle aynı oylar farklı bir temsil dağılımı oluşturabilir.' },
|
|
37
|
+
{ type: 'paragraph', html: 'Hare en büyük kalan yöntemi, geçerli oyların sandalye sayısına bölünmesiyle başlayan bir kotaya dayanır. Her liste önce tam kotalarını alır, kalan sandalyeler de en büyük kalanlara gider. Gerçek seçimlerde yasal barajı ve eşitlik kurallarını ayrıca kontrol edin.' },
|
|
38
|
+
{ type: 'table', headers: ['Yöntem', 'Dağıtım adımı', 'Yararlı soru'], rows: [['D\'Hondt', 'Oyları 1, 2, 3 ve devamına bölüp sıralayın.', 'Büyük listeleri ne kadar destekler?'], ['Sainte-Laguë', 'Oyları tek bölenlere bölüp sıralayın.', 'Daha dengeli bölen dizisi neyi değiştirir?'], ['Hare en büyük kalan', 'Tam kotaları verin, sonra kalanları sıralayın.', 'Son sandalyeyi kalan belirlediğinde kim kazanır?']] },
|
|
39
|
+
{ type: 'title', text: 'Barajları ve bölgeleri açık varsayımlar olarak test edin', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: 'Baraj, girilen değerin altında kalan parti paylarını dağıtımdan önce dışlar. Dışlanan oy sayısı görünür kalır. Gözlenen sandalye barajı, bu hesapta sandalye alan bir listenin en düşük oy payıdır; hukuki bir kural iddiası değildir.' },
|
|
41
|
+
{ type: 'list', items: ['İncelediğiniz meclisin resmî sandalye sayısıyla başlayın.', 'Barajı yalnızca modellediğiniz seçim kuralında varsa kullanın.', 'Yöntemleri aynı oy ve sandalye sayılarıyla karşılaştırın.', 'Duyarlılık kartlarıyla sınıra yakın listeleri bulun.'] },
|
|
42
|
+
{ type: 'tip', title: 'Bölge varsayımını doğru okuyun', html: 'Bölge modu aynı ulusal oy profilini girilen her bölgede tekrarlar. Bölge büyüklüğünün etkisini ayırır; yerel değişimleri, bölgesel partileri, ayrılmış sandalyeleri, telafi sandalyelerini veya koalisyon kurallarını göstermez.' },
|
|
43
|
+
{ type: 'title', text: 'Bu modelin belirleyemeyecekleri', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: 'Seçim hukuku yargı bölgesine göre değişir. Gerçek bir sayımda farklı bir baraj temeli, Sainte-Laguë çeşidi, birden fazla dağıtım aşaması, bölgesel düzeltmeler, aday kuralları veya rastgele eşitlik çözümü kullanılabilir. Gerçek bir sonucu açıklamadan önce yetkili seçim kurumunu ve ilgili mevzuatı kontrol edin.' },
|
|
45
|
+
{ type: 'tip', title: 'Şaşkınlığı kontrole dönüştürün', html: 'Bölüm ve kalan tabloları bir denetim izi oluşturur. Bir sandalye beklenmedik görünüyorsa, kazanamayan sonraki bölüm veya kalanı inceleyin ve ilgili bölgenin kesin kuralıyla karşılaştırın.' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliography } from '../bibliography';
|
|
4
|
+
import type { ElectionSeatLocaleContent } from '../entry';
|
|
5
|
+
import type { ElectionSeatUI } from '../ui';
|
|
6
|
+
|
|
7
|
+
const ui: ElectionSeatUI = {
|
|
8
|
+
allocationTitle: '席位分配',
|
|
9
|
+
eventsTitle: '下一席位在这里决定',
|
|
10
|
+
sensitivityTitle: '阈值附近的变化',
|
|
11
|
+
methodLabel: '分配方法', dhondtLabel: '顿特法最高平均数', sainteLagueLabel: '圣拉古法最高平均数', hareLabel: '黑尔配额与最大余数', partiesLabel: '各政党或名单得票', partyNameLabel: '政党或名单名称', votesLabel: '票数', addParty: '添加政党', removeParty: '删除政党', shareResult: '复制分享链接', shareCopied: '分享链接已复制。', shareCopyFallback: '请从对话框中复制分享链接。', invalidSharedResult: '分享的结果链接无效,已显示示例。', totalSeatsLabel: '待分配席位', thresholdLabel: '资格门槛', districtsLabel: '可选选区分组', districtsHelp: '用逗号分隔输入各选区席位数。模型会在每个选区使用相同的得票配置。', districtsPlaceholder: '例如:10, 8, 7', reset: '重置示例', allocatedSeatsLabel: '已分配席位', voteShareLabel: '得票率', seatShareLabel: '席位率', excludedLabel: '因门槛被排除', chamberLabel: '席位分配', quotientLabel: '商', remainderLabel: '余数', eventLabel: '席位授予顺序', eligibleVotesLabel: '计入的票数', excludedVotesLabel: '排除的票数', effectiveThresholdLabel: '观察到的席位门槛', sensitivityLabel: '门槛敏感度', lowerThresholdLabel: '门槛降低一个百分点', higherThresholdLabel: '门槛提高一个百分点', changedLabel: '发生变化的席位', noChangesLabel: '这项测试中没有席位变化', districtAssumption: '选区模式只是一个情景:它在每个选区重复输入的全国得票配置,不会替代官方选区结果。', modelNotice: '仅供教育使用的模型,不是官方选举结果、法律建议或预测。', emptyState: '输入票数后即可查看席位构成。', invalidNumber: '请输入有效的非负数字。', seatUnit: '席',
|
|
12
|
+
};
|
|
13
|
+
const faq = [
|
|
14
|
+
{ question: '这是官方选举计算器吗?', answer: '不是。这是离线教育模型。实际计票可能包含法定门槛、地区规则、保留席位、舍入方式和同票处理程序,而通用模型并不了解这些条件。' },
|
|
15
|
+
{ question: '顿特法、圣拉古法和黑尔法有什么区别?', answer: '顿特法和圣拉古法会排列连续的商。顿特法使用1、2、3等除数,圣拉古法使用奇数除数。黑尔法先分配完整配额,再按最大余数分配剩余席位。' },
|
|
16
|
+
{ question: '选区选项采用什么假设?', answer: '它会在每个选区重复输入的得票配置,并分别分配各选区席位。它用于研究选区规模的影响,不用于还原地方得票结果。' },
|
|
17
|
+
{ question: '可以分享一个固定情景吗?', answer: '可以。输入情景后复制分享链接。result 参数会保存政党、票数、席位、方法、门槛和选区,让另一位读者打开相同模型。' },
|
|
18
|
+
];
|
|
19
|
+
const howTo = [
|
|
20
|
+
{ name: '输入票数', text: '添加每个政党或名单,并输入其票数。' },
|
|
21
|
+
{ name: '选择分配规则', text: '选择顿特法、圣拉古法或黑尔最大余数法。' },
|
|
22
|
+
{ name: '设置席位和门槛', text: '输入可用席位数量,以及情景需要的资格门槛。' },
|
|
23
|
+
{ name: '阅读分配结果', text: '查看席位构成、比例、授予顺序、排除票数和门槛敏感度。' },
|
|
24
|
+
{ name: '分享情景', text: '复制链接,把完全相同的输入交给另一位读者。' },
|
|
25
|
+
];
|
|
26
|
+
const softwareApplication: SoftwareApplication = { '@type': 'SoftwareApplication', name: '选举席位分配计算器', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', description: '使用顿特法、圣拉古法和黑尔最大余数法模拟比例席位分配。', url: 'https://gamebob.dev/zh/election-seat-apportionment-calculator', offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } };
|
|
27
|
+
const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
|
|
28
|
+
const howToSchema: HowTo = { '@type': 'HowTo', name: '建立比例席位分配模型', step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) };
|
|
29
|
+
|
|
30
|
+
export const content: ToolLocaleContent<ElectionSeatLocaleContent['ui']> = {
|
|
31
|
+
slug: 'election-seat-apportionment-calculator', title: '选举席位分配计算器', description: '使用顿特法、圣拉古法或黑尔最大余数法把票数转换为席位,在透明的离线情景中比较门槛和选区规模。', ui,
|
|
32
|
+
seo: [
|
|
33
|
+
{ type: 'title', text: '从得票到代表席位', level: 2 },
|
|
34
|
+
{ type: 'paragraph', html: '席位分配把计票结果与代表机构的组成连接起来。输入政党或名单、得票数和可用席位数量,计算器会展示席位构成、比例,以及每个席位被授予的可追溯顺序。这是用于学习和解释情景的模型,不是对当选者的正式认定。' },
|
|
35
|
+
{ type: 'title', text: '用相同数据比较三种方法', level: 2 },
|
|
36
|
+
{ type: 'paragraph', html: '顿特法和圣拉古法都属于最高平均数方法。每个可能的下一席位都会产生一个商,然后选择最大的商。顿特法使用1、2、3等除数,圣拉古法使用1、3、5等奇数除数。因此相同的票数可能产生不同的代表构成。' },
|
|
37
|
+
{ type: 'paragraph', html: '黑尔最大余数法先用计入的票数除以席位数得到配额。每个名单先获得完整配额,剩余席位再按余数从大到小分配。分析真实选举时,还要检查法定门槛和同票处理规则。' },
|
|
38
|
+
{ type: 'table', headers: ['方法', '分配步骤', '可以思考的问题'], rows: [['顿特法', '将票数除以1、2、3等并排序。', '这种方法对大名单有多大帮助?'], ['圣拉古法', '将票数除以奇数除数并排序。', '更均衡的除数序列会改变什么?'], ['黑尔最大余数', '先分配完整配额,再排列余数。', '当余数决定最后席位时谁会受益?']] },
|
|
39
|
+
{ type: 'title', text: '把门槛和选区作为明确假设进行测试', level: 2 },
|
|
40
|
+
{ type: 'paragraph', html: '门槛会在分配席位前排除低于输入比例的政党。被排除的票数仍然可见。观察到的席位门槛是本次计算中获得席位的名单里最低的得票率,不是对法律规则的判断。' },
|
|
41
|
+
{ type: 'list', items: ['从所研究机构的官方席位数量开始。', '只有当被研究的选举规则确实有门槛时才输入门槛。', '比较方法时保持票数和席位数量不变。', '使用敏感度卡片寻找接近门槛的名单。'] },
|
|
42
|
+
{ type: 'tip', title: '正确理解选区情景', html: '选区模式会在每个输入选区重复相同的全国得票配置。它能隔离选区规模的影响,但不能表示地方变化、地区政党、保留席位、补偿席位或联盟规则。' },
|
|
43
|
+
{ type: 'title', text: '这个模型无法决定什么', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: '选举法因司法管辖区而异。真实计票可能使用不同的门槛基础、圣拉古法变体、多阶段分配、地区修正、候选人规则或随机同票处理。解释真实结果前,请查阅主管选举机构和适用法律。' },
|
|
45
|
+
{ type: 'tip', title: '把意外变成检查', html: '商和余数表构成一条审计线索。如果某个席位看起来出乎意料,请查看下一个落选的商或余数,并与该地区的准确规则进行比较。' },
|
|
46
|
+
],
|
|
47
|
+
faq, bibliography, howTo, schemas: [softwareApplication, faqSchema, howToSchema] as unknown as Record<string, unknown>[],
|
|
48
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { electionSeatApportionmentCalculator } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
export * from './logic';
|
|
6
|
+
export const ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL: ToolDefinition = {
|
|
7
|
+
entry: electionSeatApportionmentCalculator,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { calculateApportionment, type ElectionInput } from './logic';
|
|
3
|
+
|
|
4
|
+
const baseInput: ElectionInput = {
|
|
5
|
+
parties: [
|
|
6
|
+
{ name: 'A', votes: 500 },
|
|
7
|
+
{ name: 'B', votes: 300 },
|
|
8
|
+
{ name: 'C', votes: 200 },
|
|
9
|
+
],
|
|
10
|
+
totalSeats: 5,
|
|
11
|
+
thresholdPercent: 0,
|
|
12
|
+
method: 'dhondt',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('calculateApportionment', () => {
|
|
16
|
+
it('allocates D Hondt seats by descending quotients', () => {
|
|
17
|
+
const result = calculateApportionment(baseInput);
|
|
18
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([3, 1, 1]);
|
|
19
|
+
expect(result.events).toHaveLength(5);
|
|
20
|
+
expect(result.events[0]?.partyName).toBe('A');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('uses odd divisors for Sainte Lague and can change the chamber', () => {
|
|
24
|
+
const result = calculateApportionment({ ...baseInput, method: 'sainte-lague', totalSeats: 4 });
|
|
25
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([2, 1, 1]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('allocates exact Hare quotas without remainder seats', () => {
|
|
29
|
+
const result = calculateApportionment({
|
|
30
|
+
...baseInput,
|
|
31
|
+
parties: [{ name: 'A', votes: 400 }, { name: 'B', votes: 300 }, { name: 'C', votes: 300 }],
|
|
32
|
+
totalSeats: 10,
|
|
33
|
+
method: 'hare',
|
|
34
|
+
});
|
|
35
|
+
expect(result.districts).toEqual([]);
|
|
36
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([4, 3, 3]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('uses largest remainders and input order to resolve a tie', () => {
|
|
40
|
+
const result = calculateApportionment({
|
|
41
|
+
...baseInput,
|
|
42
|
+
parties: [{ name: 'A', votes: 300 }, { name: 'B', votes: 350 }, { name: 'C', votes: 350 }],
|
|
43
|
+
totalSeats: 4,
|
|
44
|
+
method: 'hare',
|
|
45
|
+
});
|
|
46
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([1, 2, 1]);
|
|
47
|
+
expect(result.events.at(-1)?.partyName).toBe('B');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('excludes parties below the threshold and reports excluded votes', () => {
|
|
51
|
+
const result = calculateApportionment({
|
|
52
|
+
...baseInput,
|
|
53
|
+
parties: [{ name: 'A', votes: 600 }, { name: 'B', votes: 300 }, { name: 'C', votes: 100 }],
|
|
54
|
+
totalSeats: 5,
|
|
55
|
+
thresholdPercent: 20,
|
|
56
|
+
});
|
|
57
|
+
expect(result.excludedVotes).toBe(100);
|
|
58
|
+
expect(result.partyAllocations[2]?.excluded).toBe(true);
|
|
59
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([4, 1, 0]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles zero vote parties deterministically', () => {
|
|
63
|
+
const result = calculateApportionment({
|
|
64
|
+
...baseInput,
|
|
65
|
+
parties: [{ name: 'A', votes: 100 }, { name: 'B', votes: 0 }, { name: 'C', votes: 0 }],
|
|
66
|
+
totalSeats: 3,
|
|
67
|
+
});
|
|
68
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([3, 0, 0]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('allocates each optional district separately', () => {
|
|
72
|
+
const result = calculateApportionment({ ...baseInput, districtSeats: [2, 3] });
|
|
73
|
+
expect(result.districts.map((district) => district.seats)).toEqual([2, 3]);
|
|
74
|
+
expect(result.partyAllocations.map((party) => party.seats)).toEqual([3, 2, 0]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns threshold sensitivity comparisons', () => {
|
|
78
|
+
const result = calculateApportionment({ ...baseInput, thresholdPercent: 5 });
|
|
79
|
+
expect(result.sensitivity.lower.thresholdPercent).toBe(4);
|
|
80
|
+
expect(result.sensitivity.higher.thresholdPercent).toBe(6);
|
|
81
|
+
expect(result.sensitivity.lower.seatsByParty).toHaveProperty('A');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('rejects malformed or incomplete scenarios', () => {
|
|
85
|
+
expect(() => calculateApportionment({ ...baseInput, totalSeats: 0 })).toThrow('Total seats');
|
|
86
|
+
expect(() => calculateApportionment({ ...baseInput, parties: [{ name: 'A', votes: 1 }] })).toThrow('two parties');
|
|
87
|
+
expect(() => calculateApportionment({ ...baseInput, districtSeats: [2, 2] })).toThrow('add up');
|
|
88
|
+
expect(() => calculateApportionment({ ...baseInput, parties: [{ name: 'A', votes: 0 }, { name: 'B', votes: 0 }, { name: 'C', votes: 0 }] })).toThrow('at least one vote');
|
|
89
|
+
});
|
|
90
|
+
});
|