@jjlmoya/utils-civic 1.5.0 → 1.7.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.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +4 -0
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/participatory-budget-allocator/bibliography.astro +6 -0
  8. package/src/tool/participatory-budget-allocator/bibliography.ts +8 -0
  9. package/src/tool/participatory-budget-allocator/component.astro +61 -0
  10. package/src/tool/participatory-budget-allocator/contract.test.ts +13 -0
  11. package/src/tool/participatory-budget-allocator/controller.ts +108 -0
  12. package/src/tool/participatory-budget-allocator/dom-views.test.ts +14 -0
  13. package/src/tool/participatory-budget-allocator/dom-views.ts +58 -0
  14. package/src/tool/participatory-budget-allocator/entry.ts +27 -0
  15. package/src/tool/participatory-budget-allocator/evaluator.ts +24 -0
  16. package/src/tool/participatory-budget-allocator/i18n/de.ts +44 -0
  17. package/src/tool/participatory-budget-allocator/i18n/en.ts +79 -0
  18. package/src/tool/participatory-budget-allocator/i18n/es.ts +43 -0
  19. package/src/tool/participatory-budget-allocator/i18n/fr.ts +43 -0
  20. package/src/tool/participatory-budget-allocator/i18n/id.ts +43 -0
  21. package/src/tool/participatory-budget-allocator/i18n/it.ts +43 -0
  22. package/src/tool/participatory-budget-allocator/i18n/ja.ts +43 -0
  23. package/src/tool/participatory-budget-allocator/i18n/ko.ts +43 -0
  24. package/src/tool/participatory-budget-allocator/i18n/nl.ts +43 -0
  25. package/src/tool/participatory-budget-allocator/i18n/pl.ts +43 -0
  26. package/src/tool/participatory-budget-allocator/i18n/pt.ts +43 -0
  27. package/src/tool/participatory-budget-allocator/i18n/ru.ts +43 -0
  28. package/src/tool/participatory-budget-allocator/i18n/sv.ts +43 -0
  29. package/src/tool/participatory-budget-allocator/i18n/tr.ts +43 -0
  30. package/src/tool/participatory-budget-allocator/i18n/zh.ts +43 -0
  31. package/src/tool/participatory-budget-allocator/index.ts +11 -0
  32. package/src/tool/participatory-budget-allocator/logic.test.ts +48 -0
  33. package/src/tool/participatory-budget-allocator/logic.ts +146 -0
  34. package/src/tool/participatory-budget-allocator/participatory-budget-allocator.css +426 -0
  35. package/src/tool/participatory-budget-allocator/seo.astro +14 -0
  36. package/src/tool/participatory-budget-allocator/storage.ts +32 -0
  37. package/src/tool/participatory-budget-allocator/ui.ts +88 -0
  38. package/src/tool/public-budget-analyzer/bibliography.astro +15 -0
  39. package/src/tool/public-budget-analyzer/bibliography.ts +7 -0
  40. package/src/tool/public-budget-analyzer/component.astro +52 -0
  41. package/src/tool/public-budget-analyzer/controller.ts +84 -0
  42. package/src/tool/public-budget-analyzer/dom-views.ts +52 -0
  43. package/src/tool/public-budget-analyzer/entry.ts +27 -0
  44. package/src/tool/public-budget-analyzer/evaluator.ts +15 -0
  45. package/src/tool/public-budget-analyzer/i18n/de.ts +115 -0
  46. package/src/tool/public-budget-analyzer/i18n/en.ts +78 -0
  47. package/src/tool/public-budget-analyzer/i18n/es.ts +115 -0
  48. package/src/tool/public-budget-analyzer/i18n/fr.ts +115 -0
  49. package/src/tool/public-budget-analyzer/i18n/id.ts +115 -0
  50. package/src/tool/public-budget-analyzer/i18n/it.ts +115 -0
  51. package/src/tool/public-budget-analyzer/i18n/ja.ts +115 -0
  52. package/src/tool/public-budget-analyzer/i18n/ko.ts +115 -0
  53. package/src/tool/public-budget-analyzer/i18n/nl.ts +115 -0
  54. package/src/tool/public-budget-analyzer/i18n/pl.ts +115 -0
  55. package/src/tool/public-budget-analyzer/i18n/pt.ts +115 -0
  56. package/src/tool/public-budget-analyzer/i18n/ru.ts +115 -0
  57. package/src/tool/public-budget-analyzer/i18n/sv.ts +115 -0
  58. package/src/tool/public-budget-analyzer/i18n/tr.ts +115 -0
  59. package/src/tool/public-budget-analyzer/i18n/zh.ts +115 -0
  60. package/src/tool/public-budget-analyzer/index.ts +11 -0
  61. package/src/tool/public-budget-analyzer/logic.test.ts +81 -0
  62. package/src/tool/public-budget-analyzer/logic.ts +249 -0
  63. package/src/tool/public-budget-analyzer/public-budget-analyzer.css +461 -0
  64. package/src/tool/public-budget-analyzer/seo.astro +15 -0
  65. package/src/tool/public-budget-analyzer/storage.ts +28 -0
  66. package/src/tool/public-budget-analyzer/ui.ts +78 -0
  67. package/src/tools.ts +4 -0
@@ -0,0 +1,115 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { PublicBudgetLocaleContent } from '../entry';
4
+ import type { PublicBudgetUI } from '../ui';
5
+
6
+ const ui: PublicBudgetUI = {
7
+ uploadLabel: 'Перетащите сюда CSV или JSON с бюджетом',
8
+ uploadHelp: 'Обязательные поля: категория и сумма. Необязательные: население, период, priorAmount, валюта и родительская категория.',
9
+ loadExample: 'Загрузить пример бюджета',
10
+ supportedFormat: 'CSV или JSON, обработка только в этом браузере',
11
+ tableHeading: 'Исходные строки и суммы по категориям',
12
+ categoryLabel: 'Категория',
13
+ amountLabel: 'Сумма',
14
+ populationLabel: 'Население',
15
+ sourceRowsLabel: 'Исходные строки',
16
+ analyzeEmpty: 'Выберите файл или загрузите пример, чтобы изучить бюджет.',
17
+ resultHeading: 'Карта бюджета',
18
+ totalLabel: 'Общие расходы',
19
+ perCapitaLabel: 'Расходы на человека',
20
+ variationLabel: 'Изменение по сравнению с прошлым периодом',
21
+ topCategoryLabel: 'Крупнейшая категория',
22
+ shareLabel: 'Доля от общей суммы',
23
+ sourceLabel: 'Исходные строки',
24
+ noData: 'Пока нет допустимых бюджетных строк.',
25
+ warningsHeading: 'Предупреждения для проверки',
26
+ errorsHeading: 'Не включённые строки',
27
+ methodHeading: 'Применённый метод',
28
+ methodText: 'Инструмент проверяет обязательные поля, сохраняет каждую исходную строку, объединяет одинаковые пути категорий и считает суммы из нормализованных значений. Доля категории равна её сумме, делённой на общую сумму, если общая сумма положительна. Расходы на человека делят общую сумму на первое согласованное положительное значение населения. Изменение периода равно разнице, делённой на абсолютное значение прошлой суммы.',
29
+ limitsHeading: 'Чего этот инструмент не делает',
30
+ limitsText: 'Он не доказывает, что бюджет полон, сопоставим, законен, эффективен, справедлив, беспристрастен или правильно классифицирован. Он не проверяет источник, не оценивает качество политики, не конвертирует валюты и не определяет, является ли строка ассигнованием, обязательством, платежом или прогнозом.',
31
+ edgeCasesHeading: 'Особые случаи и предупреждения данных',
32
+ edgeCasesText: 'Смешанные валюты, меняющееся население, дубли категорий, отрицательные корректировки, родительские строки рядом с дочерними, местные форматы чисел и нулевые прошлые суммы требуют проверки человеком. Браузер сохраняет исходный номер строки, чтобы каждый итог можно было проследить до входных данных.',
33
+ downloadCsv: 'Скачать проверенную таблицу',
34
+ printAction: 'Печатать анализ',
35
+ rowsLabel: 'допустимых строк',
36
+ categoriesLabel: 'категорий',
37
+ positiveTotalLabel: 'визуализированные положительные суммы',
38
+ previousTotalLabel: 'Прошлая сумма',
39
+ invalidFile: 'Не удалось прочитать файл как непустую таблицу CSV или JSON.',
40
+ fileReadError: 'Не удалось прочитать файл в этом браузере.',
41
+ exampleName: 'Пример муниципального бюджета',
42
+ noPreviousData: 'Сопоставимая сумма за прошлый период не указана.',
43
+ };
44
+
45
+ const softwareApplication: SoftwareApplication = {
46
+ '@type': 'SoftwareApplication',
47
+ name: 'Анализатор государственного бюджета',
48
+ applicationCategory: 'EducationalApplication',
49
+ operatingSystem: 'Any',
50
+ description: 'Локально изучайте государственные расходы, доли категорий, суммы на человека и изменения между периодами.',
51
+ url: 'https://gamebob.dev/ru/analiz-publichnogo-byudzheta-raskhody',
52
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
53
+ };
54
+
55
+ const faqPage: FAQPage = {
56
+ '@type': 'FAQPage',
57
+ mainEntity: [
58
+ { '@type': 'Question', name: 'Какие столбцы нужны анализатору?', acceptedAnswer: { '@type': 'Answer', text: 'В каждой строке нужны категория и числовая сумма. Население, период, priorAmount, валюта и родительская категория необязательны.' } },
59
+ { '@type': 'Question', name: 'Как рассчитываются расходы на человека?', acceptedAnswer: { '@type': 'Answer', text: 'Общая сумма делится на первое согласованное положительное значение населения. Разные знаменатели сохраняются и вызывают предупреждение.' } },
60
+ { '@type': 'Question', name: 'Что происходит с дублирующимися категориями?', acceptedAnswer: { '@type': 'Answer', text: 'Строки с одинаковым путём категории складываются, а номера исходных строк остаются видимыми, чтобы отличить реальное разделение от двойного счёта.' } },
61
+ { '@type': 'Question', name: 'Доказывает ли инструмент, что бюджет хороший или справедливый?', acceptedAnswer: { '@type': 'Answer', text: 'Нет. Он описывает предоставленные числа и не устанавливает полноту, законность, эффективность, справедливость, классификацию или результаты.' } },
62
+ ],
63
+ };
64
+
65
+ const howTo: HowTo = {
66
+ '@type': 'HowTo',
67
+ name: 'Анализ таблицы государственного бюджета',
68
+ step: [
69
+ { '@type': 'HowToStep', name: 'Подготовьте таблицу', text: 'Создайте таблицу CSV или JSON с одной категорией и суммой в каждой строке. Используйте одну валюту и единую бухгалтерскую основу.' },
70
+ { '@type': 'HowToStep', name: 'Добавьте контекст', text: 'Добавляйте население, период, priorAmount, валюту или родительскую категорию только при согласованном смысле.' },
71
+ { '@type': 'HowToStep', name: 'Загрузите файл', text: 'Перетащите файл в анализатор или загрузите пример, чтобы увидеть ожидаемую структуру и локальную проверку.' },
72
+ { '@type': 'HowToStep', name: 'Прочитайте карту', text: 'Используйте пропорциональные полосы для состава бюджета, а доступную таблицу для точных сумм, изменений и исходных строк.' },
73
+ { '@type': 'HowToStep', name: 'Проверьте предупреждения', text: 'Разберите смешанные валюты, дублирующиеся пути, отрицательные корректировки, нулевые знаменатели и родительские строки до выводов.' },
74
+ ],
75
+ };
76
+
77
+ export const content: PublicBudgetLocaleContent = {
78
+ slug: 'analiz-publichnogo-byudzheta-raskhody',
79
+ title: 'Анализатор государственного бюджета',
80
+ description: 'Локально изучайте таблицу государственного бюджета, чтобы увидеть общие расходы, доли категорий, расходы на человека, изменения и исходные строки каждого итога.',
81
+ ui,
82
+ seo: [
83
+ { type: 'title', text: 'Превратите таблицу государственного бюджета в проверяемый обзор', level: 2 },
84
+ { type: 'paragraph', html: 'В файлах государственных бюджетов часто смешаны названия категорий, бухгалтерские периоды, знаменатели населения и корректировки. Этот анализатор сохраняет исходные строки, проверяет обязательные поля и превращает те же нормализованные данные в итоги, пропорции, расходы на человека и изменения по периодам.' },
85
+ { type: 'paragraph', html: 'Результат описывает числа, но не выносит политическую оценку. Большая категория может отражать законную обязанность, разовую инвестицию, трансферт или другую бухгалтерскую границу. Обзор показывает эти различия и помогает вернуться к источнику с точным вопросом.' },
86
+ { type: 'title', text: 'Что означают расчёты', level: 2 },
87
+ { type: 'paragraph', html: 'Итог складывает все допустимые числовые суммы, включая отрицательные корректировки. Доля категории равна её сгруппированной сумме, делённой на общий итог, если итог положителен. Положительные суммы получают пропорциональные полосы, а отрицательные остаются в таблице, потому что положительная площадь изменила бы их смысл.' },
88
+ { type: 'table', headers: ['Результат', 'Расчёт', 'Вопрос для проверки'], rows: [['Общие расходы', 'Сумма нормализованных значений', 'У всех строк одна бухгалтерская основа?'], ['Доля категории', 'Сумма категории делится на положительный итог', 'Категории независимы или родительские строки перекрывают дочерние?'], ['Расходы на человека', 'Итог делится на положительный знаменатель населения', 'Население относится к тому же месту и периоду?'], ['Изменение периода', 'Текущая сумма минус прошлая делится на абсолютную прошлую', 'Периоды сопоставимы и нулевая база исключена?']] },
89
+ { type: 'title', text: 'Подготовьте сопоставимые данные', level: 2 },
90
+ { type: 'paragraph', html: 'Используйте одну валюту и явно опишите смысл суммы в каждой строке. Ассигнование, обязательство, платёж и прогноз могут быть корректными числами, но отвечают на разные вопросы. Запишите этот смысл рядом с источником, потому что анализатор не выводит его из названия столбца.' },
91
+ { type: 'list', items: ['Используйте одну строку на категорию или укажите родительскую категорию для осознанной иерархии.', 'Сохраняйте единые значения населения и не смешивайте жителей, избирателей, домохозяйства или пользователей услуг.', 'Добавляйте priorAmount только при тех же категориях и основе в прошлом периоде.', 'Проверяйте дублирующиеся пути и родительские строки, чтобы избежать двойного счёта.', 'Храните проверенную таблицу вместе с источником и использованными допущениями.'] },
92
+ { type: 'tip', title: 'Доля не является оценкой приоритета', html: 'Категория с наибольшей суммой не обязательно самая важная, расточительная или успешная. Используйте обзор для масштаба и изменений, а затем изучайте примечания источника и результаты услуги.' },
93
+ { type: 'title', text: 'Читайте предупреждения до выводов', level: 2 },
94
+ { type: 'paragraph', html: 'Дублирующиеся пути группируются, чтобы итог не оказался незаметно занижен, но список исходных строк показывает, было ли это намеренно. Валюты не конвертируются. Нулевое население убирает результат на человека, а нулевая прошлая сумма убирает процент изменения, поскольку деление на ноль не имеет осмысленного толкования.' },
95
+ { type: 'paragraph', html: 'Вложенные категории требуют особой осторожности. Если таблица содержит и Образование, и Образование > Школы как суммы, сложение может посчитать одни расходы дважды. Инструмент предупреждает о вложенных путях, но только методика источника объяснит, является ли родительская строка промежуточным итогом или отдельной статьёй.' },
96
+ { type: 'title', text: 'Что сделать после первого анализа', level: 2 },
97
+ { type: 'paragraph', html: 'Используйте номера строк, чтобы вернуться к официальному файлу и записать каждую корректировку или интерпретацию. Сравнивайте одинаковые по смыслу периоды, сохраняйте отрицательные корректировки и повторите анализ после удаления промежуточных итогов, если источник подтверждает, что они уже включены.' },
98
+ { type: 'tip', title: 'Сохраняйте границу данных видимой', html: 'Это локальный описательный анализ. Он не проверяет полноту файла, не классифицирует расходы по официальному стандарту, не согласует валюты и не доказывает причинную связь расходов с результатами.' },
99
+ ],
100
+ faq: [
101
+ { question: 'Какие столбцы нужны анализатору?', answer: 'В каждой строке нужны категория и числовая сумма. Население, период, priorAmount, валюта и родительская категория необязательны.' },
102
+ { question: 'Как рассчитываются расходы на человека?', answer: 'Итог делится на первое согласованное положительное значение населения. Разные знаменатели вызывают предупреждение.' },
103
+ { question: 'Что происходит с дублирующимися категориями?', answer: 'Одинаковые пути категорий складываются, а исходные строки остаются видимыми для проверки.' },
104
+ { question: 'Доказывает ли инструмент, что бюджет хороший или справедливый?', answer: 'Нет. Он описывает числа и не устанавливает полноту, законность, эффективность, справедливость или результаты.' },
105
+ ],
106
+ bibliography,
107
+ howTo: [
108
+ { name: 'Подготовьте таблицу', text: 'Создайте CSV или JSON с категорией и суммой в каждой строке, используя одну валюту и бухгалтерскую основу.' },
109
+ { name: 'Добавьте контекст', text: 'Добавляйте население, период, priorAmount, валюту или родительскую категорию только при согласованном смысле.' },
110
+ { name: 'Загрузите файл', text: 'Перетащите файл или загрузите пример, чтобы увидеть ожидаемую структуру.' },
111
+ { name: 'Прочитайте карту', text: 'Используйте пропорциональные полосы для состава, а таблицу для значений, изменений и исходных строк.' },
112
+ { name: 'Проверьте предупреждения', text: 'Разберите смешанные валюты, дубли, отрицательные корректировки, нулевые знаменатели и родительские строки до выводов.' },
113
+ ],
114
+ schemas: [softwareApplication, faqPage, howTo] as unknown as Record<string, unknown>[],
115
+ };
@@ -0,0 +1,115 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { PublicBudgetLocaleContent } from '../entry';
4
+ import type { PublicBudgetUI } from '../ui';
5
+
6
+ const ui: PublicBudgetUI = {
7
+ uploadLabel: 'Släpp en CSV eller JSON budgetfil här',
8
+ uploadHelp: 'Obligatoriska fält: kategori och belopp. Valfria fält: befolkning, period, priorAmount, valuta och överordnad kategori.',
9
+ loadExample: 'Ladda exempelbudget',
10
+ supportedFormat: 'CSV eller JSON, behandlas bara i den här webbläsaren',
11
+ tableHeading: 'Källrader och kategorisummor',
12
+ categoryLabel: 'Kategori',
13
+ amountLabel: 'Belopp',
14
+ populationLabel: 'Befolkning',
15
+ sourceRowsLabel: 'Källrader',
16
+ analyzeEmpty: 'Välj en fil eller ladda exemplet för att granska en budget.',
17
+ resultHeading: 'Budgetlandskap',
18
+ totalLabel: 'Totala utgifter',
19
+ perCapitaLabel: 'Utgifter per person',
20
+ variationLabel: 'Förändring från föregående period',
21
+ topCategoryLabel: 'Största kategori',
22
+ shareLabel: 'Andel av totalen',
23
+ sourceLabel: 'Källrader',
24
+ noData: 'Det finns ännu inga giltiga budgetrader.',
25
+ warningsHeading: 'Varningar att granska',
26
+ errorsHeading: 'Rader som inte togs med',
27
+ methodHeading: 'Använd metod',
28
+ methodText: 'Verktyget validerar obligatoriska fält, behåller varje källrad, grupperar identiska kategorisökvägar och räknar summor från normaliserade belopp. En kategoris andel är beloppet delat med totalen när totalen är positiv. Utgifter per person delar totalen med det första konsekventa positiva befolkningstalet. Periodförändringen är skillnaden delad med det föregående beloppets absoluta värde.',
29
+ limitsHeading: 'Vad verktyget inte gör',
30
+ limitsText: 'Det bevisar inte att en budget är fullständig, jämförbar, laglig, effektiv, rättvis, opartisk eller korrekt klassificerad. Det granskar inte källan, bedömer inte politik, växlar inte valutor och avgör inte om en rad är ett anslag, åtagande, en betalning eller en prognos.',
31
+ edgeCasesHeading: 'Specialfall och datavarningar',
32
+ edgeCasesText: 'Blandade valutor, skiftande befolkningstal, dubbla kategorier, negativa justeringar, överordnade rader bredvid underordnade rader, lokala talformat och föregående belopp på noll kräver mänsklig kontroll. Webbläsaren sparar det ursprungliga radnumret så att varje aggregat kan spåras till indata.',
33
+ downloadCsv: 'Ladda ner granskad tabell',
34
+ printAction: 'Skriv ut analys',
35
+ rowsLabel: 'giltiga rader',
36
+ categoriesLabel: 'kategorier',
37
+ positiveTotalLabel: 'visualiserade positiva belopp',
38
+ previousTotalLabel: 'Föregående total',
39
+ invalidFile: 'Filen kunde inte tolkas som en icke tom CSV eller JSON tabell.',
40
+ fileReadError: 'Filen kunde inte läsas i den här webbläsaren.',
41
+ exampleName: 'Exempel på kommunal budget',
42
+ noPreviousData: 'Inget jämförbart belopp från föregående period angavs.',
43
+ };
44
+
45
+ const softwareApplication: SoftwareApplication = {
46
+ '@type': 'SoftwareApplication',
47
+ name: 'Analys av offentlig budget',
48
+ applicationCategory: 'EducationalApplication',
49
+ operatingSystem: 'Any',
50
+ description: 'Granska offentliga utgifter, kategoriandelar, belopp per person och förändringar mellan perioder lokalt.',
51
+ url: 'https://gamebob.dev/sv/analys-offentlig-budget-utgifter',
52
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
53
+ };
54
+
55
+ const faqPage: FAQPage = {
56
+ '@type': 'FAQPage',
57
+ mainEntity: [
58
+ { '@type': 'Question', name: 'Vilka kolumner behöver analysverktyget?', acceptedAnswer: { '@type': 'Answer', text: 'Varje rad behöver en kategori och ett numeriskt belopp. Befolkning, period, priorAmount, valuta och överordnad kategori är valfria.' } },
59
+ { '@type': 'Question', name: 'Hur beräknas utgifter per person?', acceptedAnswer: { '@type': 'Answer', text: 'Totalbeloppet delas med det första konsekventa positiva befolkningstalet. Olika nämnare visas och ger en varning.' } },
60
+ { '@type': 'Question', name: 'Vad händer med dubbla kategorier?', acceptedAnswer: { '@type': 'Answer', text: 'Rader med samma kategorisökväg summeras och deras källradnummer visas så att verklig uppdelning kan skiljas från dubbelräkning.' } },
61
+ { '@type': 'Question', name: 'Kan detta bevisa att en budget är bra eller rättvis?', acceptedAnswer: { '@type': 'Answer', text: 'Nej. Verktyget beskriver de angivna siffrorna och fastställer inte fullständighet, laglighet, effektivitet, rättvisa, klassificering eller resultat.' } },
62
+ ],
63
+ };
64
+
65
+ const howTo: HowTo = {
66
+ '@type': 'HowTo',
67
+ name: 'Analysera en offentlig budgettabell',
68
+ step: [
69
+ { '@type': 'HowToStep', name: 'Förbered en tabell', text: 'Skapa en CSV eller JSON tabell med en kategori och ett belopp per rad. Använd samma valuta och redovisningsgrund.' },
70
+ { '@type': 'HowToStep', name: 'Lägg till sammanhang', text: 'Lägg bara till befolkning, period, priorAmount, valuta eller överordnad kategori när betydelsen är konsekvent.' },
71
+ { '@type': 'HowToStep', name: 'Ladda filen', text: 'Släpp filen i analysverktyget eller ladda exemplet för att se förväntad struktur och lokal validering.' },
72
+ { '@type': 'HowToStep', name: 'Läs landskapet', text: 'Använd proportionella staplar för sammansättningen och den tillgängliga tabellen för exakta belopp, ändringar och källrader.' },
73
+ { '@type': 'HowToStep', name: 'Granska varningar', text: 'Klargör blandade valutor, dubbla sökvägar, negativa justeringar, nollnämnare och överordnade rader innan du drar slutsatser.' },
74
+ ],
75
+ };
76
+
77
+ export const content: PublicBudgetLocaleContent = {
78
+ slug: 'analys-offentlig-budget-utgifter',
79
+ title: 'Analys av offentlig budget',
80
+ description: 'Granska en offentlig budgettabell lokalt för att se totala utgifter, kategoriandelar, utgifter per person, förändringar och källraderna bakom varje aggregat.',
81
+ ui,
82
+ seo: [
83
+ { type: 'title', text: 'Gör en offentlig budgettabell till en granskningsbar vy', level: 2 },
84
+ { type: 'paragraph', html: 'Offentliga budgetfiler blandar ofta kategorietiketter, redovisningsperioder, befolkningsnämnare och justeringar i en tabell som är svår att läsa snabbt. Det här verktyget behåller källraderna, kontrollerar de obligatoriska fälten och omvandlar samma normaliserade data till totaler, proportioner, utgifter per person och förändringar mellan perioder.' },
85
+ { type: 'paragraph', html: 'Resultatet är beskrivande och inte ett politiskt omdöme. En stor kategori kan spegla ett lagstadgat ansvar, en engångsinvestering, en överföring eller en annan redovisningsgräns. Vyn synliggör dessa mönster så att källdokumentet kan granskas med en bättre fråga.' },
86
+ { type: 'title', text: 'Vad beräkningarna betyder', level: 2 },
87
+ { type: 'paragraph', html: 'Totalen är summan av alla giltiga numeriska belopp, inklusive negativa justeringar. En kategoriandel är dess grupperade belopp delat med totalen när totalen är positiv. Positiva belopp får proportionella staplar; negativa belopp ligger kvar i tabellen eftersom en positiv yta skulle ändra betydelsen.' },
88
+ { type: 'table', headers: ['Resultat', 'Beräkning', 'Kontrollfråga'], rows: [['Totala utgifter', 'Summa av normaliserade belopp', 'Använder alla rader samma redovisningsgrund?'], ['Kategoriandel', 'Kategoribelopp delat med positiv total', 'Är kategorierna ömsesidigt uteslutande eller överlappar föräldrar och barn?'], ['Utgifter per person', 'Total delad med positiv befolkningsnämnare', 'Beskriver befolkningen samma plats och period?'], ['Periodförändring', 'Aktuellt belopp minus föregående delat med föregående belopps absoluta värde', 'Är perioderna jämförbara och undviks en nollbas?']] },
89
+ { type: 'title', text: 'Förbered jämförbara data', level: 2 },
90
+ { type: 'paragraph', html: 'Använd en valuta och en tydlig betydelse för beloppet i varje rad. Ett anslag, ett åtagande, en betalning och en prognos kan alla vara giltiga siffror men besvara olika frågor. Anteckna betydelsen vid källan eftersom analysverktyget inte kan härleda den från kolumnnamnet.' },
91
+ { type: 'list', items: ['Använd en rad per kategori eller ange en överordnad kategori för en avsiktlig hierarki.', 'Håll befolkningstalen konsekventa och blanda inte invånare, röstberättigade, hushåll eller tjänsteanvändare.', 'Ta med priorAmount bara när föregående period använder samma kategorier och grund.', 'Kontrollera dubbla kategorisökvägar och överordnade rader för oavsiktlig dubbelräkning.', 'Spara den granskade tabellen med originalkällan och de antaganden som användes.'] },
92
+ { type: 'tip', title: 'En andel är inte ett prioriteringsvärde', html: 'Kategorin med störst belopp är inte automatiskt viktigast, mest slösaktig eller mest framgångsrik. Använd vyn för skala och förändring och läs sedan källans noter och tjänstens resultat.' },
93
+ { type: 'title', text: 'Läs varningar innan du drar slutsatser', level: 2 },
94
+ { type: 'paragraph', html: 'Dubbla sökvägar grupperas så att aggregatet inte tyst underskattas, men listan över källrader visar om grupperingen var avsiktlig. Blandade valutor räknas aldrig om. En befolkning på noll tar bort resultatet per person och ett föregående belopp på noll tar bort förändringsprocenten eftersom division med noll saknar meningsfull tolkning.' },
95
+ { type: 'paragraph', html: 'Kapslade kategorier kräver särskild försiktighet. Om en tabell innehåller både Utbildning och Utbildning > Skolor som belopp kan en addition räkna samma utgift två gånger. Verktyget varnar för kapslade sökvägar, men bara källans metod kan visa om föräldraraden är en delsumma eller en fristående post.' },
96
+ { type: 'title', text: 'Gör så efter den första analysen', level: 2 },
97
+ { type: 'paragraph', html: 'Använd radnumren för att gå tillbaka till den officiella filen och dokumentera varje korrigering eller tolkning. Jämför likvärdiga perioder, behåll negativa justeringar som justeringar och kör analysen igen efter att ha tagit bort delsummerader när källan bekräftar att de redan ingår.' },
98
+ { type: 'tip', title: 'Håll datagränsen synlig', html: 'Detta är en lokal beskrivande analys. Den verifierar inte att filen är komplett, klassificerar inte utgifter enligt en officiell standard, stämmer inte av valutor och bevisar inte ett orsakssamband mellan utgifter och resultat.' },
99
+ ],
100
+ faq: [
101
+ { question: 'Vilka kolumner behöver analysverktyget?', answer: 'Varje rad behöver kategori och numeriskt belopp. Befolkning, period, priorAmount, valuta och överordnad kategori är valfria.' },
102
+ { question: 'Hur beräknas utgifter per person?', answer: 'Totalen delas med det första konsekventa positiva befolkningstalet. Olika nämnare ger en varning.' },
103
+ { question: 'Vad händer med dubbla kategorier?', answer: 'Samma kategorisökvägar summeras och deras källrader visas för granskning.' },
104
+ { question: 'Kan detta bevisa att en budget är bra eller rättvis?', answer: 'Nej. Det beskriver angivna siffror och kan inte fastställa fullständighet, laglighet, effektivitet, rättvisa eller resultat.' },
105
+ ],
106
+ bibliography,
107
+ howTo: [
108
+ { name: 'Förbered en tabell', text: 'Skapa en CSV eller JSON tabell med kategori och belopp per rad, i samma valuta och redovisningsgrund.' },
109
+ { name: 'Lägg till sammanhang', text: 'Lägg till befolkning, period, priorAmount, valuta eller överordnad kategori bara när betydelsen är konsekvent.' },
110
+ { name: 'Ladda filen', text: 'Släpp filen eller ladda exemplet för att se den förväntade strukturen.' },
111
+ { name: 'Läs landskapet', text: 'Använd proportionella staplar för sammansättning och tabellen för värden, ändringar och källrader.' },
112
+ { name: 'Granska varningar', text: 'Klargör blandade valutor, dubbletter, negativa justeringar, nollnämnare och överordnade rader innan du drar slutsatser.' },
113
+ ],
114
+ schemas: [softwareApplication, faqPage, howTo] as unknown as Record<string, unknown>[],
115
+ };
@@ -0,0 +1,115 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { PublicBudgetLocaleContent } from '../entry';
4
+ import type { PublicBudgetUI } from '../ui';
5
+
6
+ const ui: PublicBudgetUI = {
7
+ uploadLabel: 'Bir bütçe CSV veya JSON dosyasını buraya bırakın',
8
+ uploadHelp: 'Gerekli alanlar: kategori ve tutar. İsteğe bağlı: nüfus, dönem, priorAmount, para birimi ve üst kategori.',
9
+ loadExample: 'Örnek bütçeyi yükle',
10
+ supportedFormat: 'CSV veya JSON, yalnızca bu tarayıcıda işlenir',
11
+ tableHeading: 'Kaynak satırları ve kategori toplamları',
12
+ categoryLabel: 'Kategori',
13
+ amountLabel: 'Tutar',
14
+ populationLabel: 'Nüfus',
15
+ sourceRowsLabel: 'Kaynak satırları',
16
+ analyzeEmpty: 'Bir bütçeyi incelemek için dosya seçin veya örneği yükleyin.',
17
+ resultHeading: 'Bütçe manzarası',
18
+ totalLabel: 'Toplam harcama',
19
+ perCapitaLabel: 'Kişi başına harcama',
20
+ variationLabel: 'Önceki döneme göre değişim',
21
+ topCategoryLabel: 'En büyük kategori',
22
+ shareLabel: 'Toplam içindeki pay',
23
+ sourceLabel: 'Kaynak satırları',
24
+ noData: 'Henüz geçerli bütçe satırı bulunmuyor.',
25
+ warningsHeading: 'İncelenecek uyarılar',
26
+ errorsHeading: 'Dahil edilmeyen satırlar',
27
+ methodHeading: 'Uygulanan yöntem',
28
+ methodText: 'Araç gerekli alanları doğrular, her kaynak satırını korur, aynı kategori yollarını gruplar ve toplamları normalleştirilmiş tutarlardan hesaplar. Kategori payı, toplam pozitif olduğunda kategori tutarının toplama bölünmesidir. Kişi başına harcama, toplamı ilk tutarlı pozitif nüfus paydasına böler. Dönem değişimi, farkın önceki tutarın mutlak değerine bölünmesiyle bulunur.',
29
+ limitsHeading: 'Bu araç ne yapmaz',
30
+ limitsText: 'Bir bütçenin eksiksiz, karşılaştırılabilir, yasal, verimli, adil, tarafsız veya doğru sınıflandırılmış olduğunu kanıtlamaz. Kaynağı denetlemez, politika kalitesini değerlendirmez, para birimlerini dönüştürmez ve bir satırın ödenek, yükümlülük, ödeme veya tahmin olup olmadığını belirlemez.',
31
+ edgeCasesHeading: 'Sınır durumları ve veri uyarıları',
32
+ edgeCasesText: 'Karışık para birimleri, değişen nüfuslar, yinelenen kategoriler, negatif düzeltmeler, alt satırlarla birlikte üst satırlar, yerel sayı biçimleri ve sıfır önceki tutarlar insan incelemesi gerektirir. Tarayıcı, her toplamın girdiye kadar izlenebilmesi için özgün satır numarasını korur.',
33
+ downloadCsv: 'İncelenen tabloyu indir',
34
+ printAction: 'Analizi yazdır',
35
+ rowsLabel: 'geçerli satır',
36
+ categoriesLabel: 'kategori',
37
+ positiveTotalLabel: 'görselleştirilen pozitif tutarlar',
38
+ previousTotalLabel: 'Önceki toplam',
39
+ invalidFile: 'Dosya boş olmayan bir CSV veya JSON tablosu olarak okunamadı.',
40
+ fileReadError: 'Dosya bu tarayıcıda okunamadı.',
41
+ exampleName: 'Belediye bütçesi örneği',
42
+ noPreviousData: 'Karşılaştırılabilir bir önceki dönem tutarı verilmedi.',
43
+ };
44
+
45
+ const softwareApplication: SoftwareApplication = {
46
+ '@type': 'SoftwareApplication',
47
+ name: 'Kamu bütçesi analiz aracı',
48
+ applicationCategory: 'EducationalApplication',
49
+ operatingSystem: 'Any',
50
+ description: 'Kamu harcamalarını, kategori paylarını, kişi başına tutarları ve dönem değişikliklerini yerel olarak inceleyin.',
51
+ url: 'https://gamebob.dev/tr/kamu-butcesi-harcama-analizi',
52
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
53
+ };
54
+
55
+ const faqPage: FAQPage = {
56
+ '@type': 'FAQPage',
57
+ mainEntity: [
58
+ { '@type': 'Question', name: 'Analiz için hangi sütunlar gerekir?', acceptedAnswer: { '@type': 'Answer', text: 'Her satırda kategori ve sayısal tutar bulunmalıdır. Nüfus, dönem, priorAmount, para birimi ve üst kategori isteğe bağlıdır.' } },
59
+ { '@type': 'Question', name: 'Kişi başına harcama nasıl hesaplanır?', acceptedAnswer: { '@type': 'Answer', text: 'Toplam tutar, bulunan ilk tutarlı pozitif nüfus değerine bölünür. Farklı paydalar korunur ve uyarı oluşturur.' } },
60
+ { '@type': 'Question', name: 'Yinelenen kategorilere ne olur?', acceptedAnswer: { '@type': 'Answer', text: 'Aynı kategori yoluna sahip satırlar toplanır ve kaynak satır numaraları görünür kalır. Böylece gerçek bir bölünme ile iki kez sayma ayrılabilir.' } },
61
+ { '@type': 'Question', name: 'Bu araç bütçenin iyi veya adil olduğunu kanıtlar mı?', acceptedAnswer: { '@type': 'Answer', text: 'Hayır. Verilen sayıları betimler; bütünlük, yasallık, verimlilik, adalet, sınıflandırma veya sonuçları belirlemez.' } },
62
+ ],
63
+ };
64
+
65
+ const howTo: HowTo = {
66
+ '@type': 'HowTo',
67
+ name: 'Kamu bütçesi tablosunu analiz etme',
68
+ step: [
69
+ { '@type': 'HowToStep', name: 'Tabloyu hazırlayın', text: 'Her satırda bir kategori ve tutar bulunan CSV veya JSON tablosu oluşturun. Para birimini ve muhasebe temelini aynı tutun.' },
70
+ { '@type': 'HowToStep', name: 'Bağlam ekleyin', text: 'Nüfus, dönem, priorAmount, para birimi veya üst kategoriyi yalnızca anlamları tutarlıysa ekleyin.' },
71
+ { '@type': 'HowToStep', name: 'Dosyayı yükleyin', text: 'Beklenen yapıyı ve yerel doğrulama sonucunu görmek için dosyayı araca bırakın veya örneği yükleyin.' },
72
+ { '@type': 'HowToStep', name: 'Manzarayı okuyun', text: 'Bütçe bileşimi için orantılı çubukları, kesin tutarlar, değişimler ve kaynak satırları için erişilebilir tabloyu kullanın.' },
73
+ { '@type': 'HowToStep', name: 'Uyarıları inceleyin', text: 'Sonuç çıkarmadan önce karışık para birimlerini, yinelenen yolları, negatif düzeltmeleri, sıfır paydaları ve üst satırları açıklığa kavuşturun.' },
74
+ ],
75
+ };
76
+
77
+ export const content: PublicBudgetLocaleContent = {
78
+ slug: 'kamu-butcesi-harcama-analizi',
79
+ title: 'Kamu bütçesi analiz aracı',
80
+ description: 'Toplam harcamayı, kategori paylarını, kişi başına harcamayı, değişimleri ve her toplamın kaynak satırlarını görmek için kamu bütçesi tablosunu yerel olarak inceleyin.',
81
+ ui,
82
+ seo: [
83
+ { type: 'title', text: 'Kamu bütçesi tablosunu denetlenebilir bir görünüme dönüştürün', level: 2 },
84
+ { type: 'paragraph', html: 'Kamu bütçesi dosyaları kategori etiketlerini, muhasebe dönemlerini, nüfus paydalarını ve düzeltmeleri çoğu zaman hızlı okunamayan tek bir tabloda birleştirir. Bu araç kaynak satırlarını korur, gerekli alanları doğrular ve aynı normalleştirilmiş verilerden toplamları, oranları, kişi başına harcamayı ve dönem değişimlerini üretir.' },
85
+ { type: 'paragraph', html: 'Sonuç betimleyicidir ve siyasi bir hüküm değildir. Büyük bir kategori yasal bir sorumluluğu, tek seferlik yatırımı, transferi veya farklı bir muhasebe sınırını yansıtabilir. Görünüm bu kalıpları açığa çıkarır ve kaynağın daha iyi bir soruyla incelenmesini sağlar.' },
86
+ { type: 'title', text: 'Hesaplamalar ne anlama gelir', level: 2 },
87
+ { type: 'paragraph', html: 'Toplam, negatif düzeltmeler dahil geçerli sayısal tutarların toplamıdır. Kategori payı, toplam pozitif olduğunda gruplanmış kategori tutarının toplama bölünmesidir. Pozitif tutarlar orantılı çubuklarla gösterilir; negatif tutarlar tabloda kalır, çünkü pozitif alan kullanmak anlamlarını değiştirir.' },
88
+ { type: 'table', headers: ['Çıktı', 'Hesaplama', 'Kontrol sorusu'], rows: [['Toplam harcama', 'Normalleştirilmiş tutarların toplamı', 'Tüm satırlar aynı muhasebe temelini mi kullanıyor?'], ['Kategori payı', 'Kategori tutarının pozitif toplama bölümü', 'Kategoriler birbirini dışlıyor mu, yoksa üst ve alt kategoriler örtüşüyor mu?'], ['Kişi başına harcama', 'Toplamın pozitif nüfus paydasına bölümü', 'Nüfus aynı yeri ve dönemi mi anlatıyor?'], ['Dönem değişimi', 'Güncel tutar eksi önceki tutarın mutlak önceki değere bölümü', 'Dönemler karşılaştırılabilir mi ve sıfır tabandan kaçınılıyor mu?']] },
89
+ { type: 'title', text: 'Karşılaştırılabilir verileri hazırlayın', level: 2 },
90
+ { type: 'paragraph', html: 'Her satır için tek bir para birimi ve tutarın açık bir anlamını kullanın. Ödenek, yükümlülük, ödeme ve tahmin geçerli sayılar olabilir, ancak farklı sorulara yanıt verir. Bu anlamı kaynağın yanında kaydedin; analiz aracı bunu sütun adından çıkaramaz.' },
91
+ { type: 'list', items: ['Her kategori için bir satır kullanın veya bilinçli bir hiyerarşi için üst kategori belirtin.', 'Nüfus değerlerini tutarlı tutun; sakinleri, seçmenleri, haneleri veya hizmet kullanıcılarını karıştırmayın.', 'priorAmount alanını yalnızca önceki dönem aynı kategorileri ve temeli kullanıyorsa ekleyin.', 'Kazara iki kez saymayı önlemek için yinelenen kategori yollarını ve üst satırları kontrol edin.', 'İncelenen tabloyu özgün kaynak ve kullanılan varsayımlarla birlikte saklayın.'] },
92
+ { type: 'tip', title: 'Oran bir öncelik puanı değildir', html: 'En yüksek tutarlı kategori otomatik olarak en önemli, en israfçı veya en başarılı kategori değildir. Ölçeği ve değişimleri bulmak için görünümü kullanın, sonra kaynak notlarını ve hizmet sonuçlarını okuyun.' },
93
+ { type: 'title', text: 'Sonuç çıkarmadan önce uyarıları okuyun', level: 2 },
94
+ { type: 'paragraph', html: 'Yinelenen yollar, toplamın sessizce düşük kalmaması için gruplanır; ancak kaynak satırı listesi bu gruplamanın bilinçli olup olmadığını gösterir. Karışık para birimleri hiçbir zaman dönüştürülmez. Sıfır nüfus kişi başına sonucu, sıfır önceki tutar ise değişim yüzdesini kaldırır, çünkü sıfıra bölmenin anlamlı bir yorumu yoktur.' },
95
+ { type: 'paragraph', html: 'İç içe kategoriler özel dikkat ister. Bir tabloda hem Eğitim hem de Eğitim > Okullar tutar olarak bulunuyorsa, bunları toplamak aynı harcamayı iki kez sayabilir. Araç iç içe yolları bildirir, ancak üst satırın ara toplam mı yoksa bağımsız bir kalem mi olduğunu yalnızca kaynak yöntemi açıklayabilir.' },
96
+ { type: 'title', text: 'İlk incelemeden sonra ne yapmalı', level: 2 },
97
+ { type: 'paragraph', html: 'Her düzeltmeyi veya yorumu belgelemek için satır numaralarıyla resmî dosyaya dönün. Benzer dönemleri karşılaştırın, negatif düzeltmeleri düzeltme olarak koruyun ve kaynak bunların zaten dahil olduğunu doğruladığında ara toplamları kaldırıp analizi yeniden çalıştırın.' },
98
+ { type: 'tip', title: 'Veri sınırını görünür tutun', html: 'Bu yerel ve betimleyici bir analizdir. Dosyanın eksiksiz olduğunu doğrulamaz, harcamayı resmî bir standarda göre sınıflandırmaz, para birimlerini uzlaştırmaz ve harcama ile sonuçlar arasında nedensel bağ kanıtlamaz.' },
99
+ ],
100
+ faq: [
101
+ { question: 'Analiz için hangi sütunlar gerekir?', answer: 'Her satırda kategori ve sayısal tutar bulunmalıdır. Nüfus, dönem, priorAmount, para birimi ve üst kategori isteğe bağlıdır.' },
102
+ { question: 'Kişi başına harcama nasıl hesaplanır?', answer: 'Toplam, ilk tutarlı pozitif nüfus değerine bölünür. Farklı paydalar uyarı oluşturur.' },
103
+ { question: 'Yinelenen kategorilere ne olur?', answer: 'Aynı kategori yolları toplanır ve kaynak satırları inceleme için görünür kalır.' },
104
+ { question: 'Bu araç bütçenin iyi veya adil olduğunu kanıtlar mı?', answer: 'Hayır. Verilen sayıları betimler ve bütünlük, yasallık, verimlilik, adalet veya sonuçları belirleyemez.' },
105
+ ],
106
+ bibliography,
107
+ howTo: [
108
+ { name: 'Tabloyu hazırlayın', text: 'Aynı para birimi ve muhasebe temeliyle, her satırda kategori ve tutar bulunan CSV veya JSON tablosu oluşturun.' },
109
+ { name: 'Bağlam ekleyin', text: 'Nüfus, dönem, priorAmount, para birimi veya üst kategoriyi yalnızca anlamları tutarlıysa ekleyin.' },
110
+ { name: 'Dosyayı yükleyin', text: 'Beklenen yapıyı görmek için dosyayı bırakın veya örneği yükleyin.' },
111
+ { name: 'Manzarayı okuyun', text: 'Bileşim için orantılı çubukları, değerler, değişimler ve kaynak satırları için tabloyu kullanın.' },
112
+ { name: 'Uyarıları inceleyin', text: 'Sonuç çıkarmadan önce karışık para birimlerini, tekrarları, negatif düzeltmeleri, sıfır paydaları ve üst satırları açıklayın.' },
113
+ ],
114
+ schemas: [softwareApplication, faqPage, howTo] as unknown as Record<string, unknown>[],
115
+ };
@@ -0,0 +1,115 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { PublicBudgetLocaleContent } from '../entry';
4
+ import type { PublicBudgetUI } from '../ui';
5
+
6
+ const ui: PublicBudgetUI = {
7
+ uploadLabel: '将预算 CSV 或 JSON 文件拖到这里',
8
+ uploadHelp: '必填字段: 类别和金额。可选字段: 人口、期间、priorAmount、货币和父类别。',
9
+ loadExample: '加载示例预算',
10
+ supportedFormat: 'CSV 或 JSON,仅在此浏览器中处理',
11
+ tableHeading: '来源行和类别合计',
12
+ categoryLabel: '类别',
13
+ amountLabel: '金额',
14
+ populationLabel: '人口',
15
+ sourceRowsLabel: '来源行',
16
+ analyzeEmpty: '选择文件或加载示例来检查预算。',
17
+ resultHeading: '预算全景',
18
+ totalLabel: '总支出',
19
+ perCapitaLabel: '人均支出',
20
+ variationLabel: '与上一期间的变化',
21
+ topCategoryLabel: '最大类别',
22
+ shareLabel: '占总额比例',
23
+ sourceLabel: '来源行',
24
+ noData: '目前还没有有效的预算行。',
25
+ warningsHeading: '需要检查的警告',
26
+ errorsHeading: '未纳入的行',
27
+ methodHeading: '使用的方法',
28
+ methodText: '工具会验证必填字段,保留每一条来源行,合并相同的类别路径,并根据标准化金额计算合计。当总额为正时,类别占比等于类别金额除以总额。人均支出使用第一个一致的正人口值作为分母。期间变化等于当前金额减去上一金额,再除以上一金额的绝对值。',
29
+ limitsHeading: '这个工具不会做什么',
30
+ limitsText: '它不会证明预算完整、可比较、合法、高效、公平、无偏或分类正确。它不会审计来源,不会评价政策质量,不会换算货币,也不会判断某一行是拨款、债务、付款还是预测。',
31
+ edgeCasesHeading: '边界情况和数据警告',
32
+ edgeCasesText: '混合货币、变化的人口、重复类别、负数调整、父行和子行并存、本地数字格式以及上一期间金额为零,都需要人工复核。浏览器保留原始行号,因此每个合计都可以追溯到输入数据。',
33
+ downloadCsv: '下载已复核表格',
34
+ printAction: '打印分析',
35
+ rowsLabel: '有效行',
36
+ categoriesLabel: '类别',
37
+ positiveTotalLabel: '已可视化的正数金额',
38
+ previousTotalLabel: '上一期间合计',
39
+ invalidFile: '文件无法被解析为空非空的 CSV 或 JSON 表格。',
40
+ fileReadError: '此浏览器无法读取文件。',
41
+ exampleName: '社区预算示例',
42
+ noPreviousData: '没有提供可比较的上一期间金额。',
43
+ };
44
+
45
+ const softwareApplication: SoftwareApplication = {
46
+ '@type': 'SoftwareApplication',
47
+ name: '公共预算分析工具',
48
+ applicationCategory: 'EducationalApplication',
49
+ operatingSystem: 'Any',
50
+ description: '在本地查看公共支出、类别占比、人均金额以及期间变化。',
51
+ url: 'https://gamebob.dev/zh/gonggong-yusuan-zhichu-fenxi',
52
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
53
+ };
54
+
55
+ const faqPage: FAQPage = {
56
+ '@type': 'FAQPage',
57
+ mainEntity: [
58
+ { '@type': 'Question', name: '分析需要哪些列?', acceptedAnswer: { '@type': 'Answer', text: '每一行都需要类别和数字金额。人口、期间、priorAmount、货币和父类别是可选的。' } },
59
+ { '@type': 'Question', name: '人均支出如何计算?', acceptedAnswer: { '@type': 'Answer', text: '总金额除以表格中第一个一致的正人口值。不同的分母会保留并产生警告。' } },
60
+ { '@type': 'Question', name: '重复类别会怎样?', acceptedAnswer: { '@type': 'Answer', text: '拥有相同类别路径的行会相加,同时保留来源行号,以便区分真实拆分和重复计算。' } },
61
+ { '@type': 'Question', name: '它能证明预算很好或公平吗?', acceptedAnswer: { '@type': 'Answer', text: '不能。它只描述提供的数字,不判断完整性、合法性、效率、公平性、分类或结果。' } },
62
+ ],
63
+ };
64
+
65
+ const howTo: HowTo = {
66
+ '@type': 'HowTo',
67
+ name: '分析公共预算表',
68
+ step: [
69
+ { '@type': 'HowToStep', name: '准备表格', text: '创建 CSV 或 JSON 表格,每行包含一个类别和金额。保持货币和会计口径一致。' },
70
+ { '@type': 'HowToStep', name: '补充背景字段', text: '只有在含义一致时,才添加人口、期间、priorAmount、货币或父类别。' },
71
+ { '@type': 'HowToStep', name: '加载文件', text: '将文件拖入工具,或加载示例来查看预期结构和本地验证结果。' },
72
+ { '@type': 'HowToStep', name: '阅读全景', text: '用比例条查看构成,用可访问表格查看准确金额、变化和来源行。' },
73
+ { '@type': 'HowToStep', name: '检查警告', text: '得出结论前,先处理混合货币、重复路径、负数调整、零分母和父行。' },
74
+ ],
75
+ };
76
+
77
+ export const content: PublicBudgetLocaleContent = {
78
+ slug: 'public-budget-analyzer',
79
+ title: '公共预算分析工具',
80
+ description: '在本地分析公共预算表,查看总支出、类别占比、人均支出、期间变化以及每个合计背后的来源行。',
81
+ ui,
82
+ seo: [
83
+ { type: 'title', text: '把公共预算表变成可核查的视图', level: 2 },
84
+ { type: 'paragraph', html: '公共预算文件经常把类别标签、会计期间、人口分母和调整金额混在一张难以快速阅读的表中。这个分析工具保留来源行,验证必填字段,并将同一份标准化数据转换为合计、类别比例、人均支出和期间变化。' },
85
+ { type: 'paragraph', html: '结果是对数字的描述,不是政策判断。一个较大的类别可能来自法定职责、一次性投资、转移支付或不同的会计边界。这个视图让这些模式可见,帮助你带着更具体的问题回到原始资料。' },
86
+ { type: 'title', text: '计算结果代表什么', level: 2 },
87
+ { type: 'paragraph', html: '总额是所有有效数字金额的总和,包括负数调整。当总额为正时,类别占比是类别合计除以总额。正数金额使用比例条表示,负数金额留在表格中,因为用正面积表示会改变它的含义。' },
88
+ { type: 'table', headers: ['结果', '计算', '核查问题'], rows: [['总支出', '标准化金额之和', '所有行是否使用相同的会计口径?'], ['类别占比', '类别金额除以正数总额', '类别是否互斥,父类别是否和子类别重叠?'], ['人均支出', '总额除以正人口分母', '人口是否对应同一个地点和期间?'], ['期间变化', '当前金额减去上一金额,再除以上一金额绝对值', '期间是否可比,是否避免了零基数?']] },
89
+ { type: 'title', text: '准备可比较的数据', level: 2 },
90
+ { type: 'paragraph', html: '每一行使用同一种货币,并明确金额的含义。预算授权、债务、付款和预测都可能是有效数字,却回答不同的问题。请在来源旁记录这种含义,因为工具无法从列名推断它。' },
91
+ { type: 'list', items: ['每个类别使用一行,或提供父类别来建立有意设计的层级。', '保持人口口径一致,不要混合居民、选民、家庭或服务使用者。', '只有上一期间使用相同类别和口径时,才加入 priorAmount。', '检查重复类别路径和父行,避免意外重复计算。', '将复核后的表格与原始来源和使用的假设一起保存。'] },
92
+ { type: 'tip', title: '比例不是优先级评分', html: '金额最大的类别不一定最重要、最浪费或最成功。用这个视图寻找规模和变化,然后阅读来源注释和服务结果。' },
93
+ { type: 'title', text: '得出结论前先阅读警告', level: 2 },
94
+ { type: 'paragraph', html: '重复路径会被合并,避免合计在没有提示的情况下偏低,但来源行列表可以帮助你确认这种合并是否有意。混合货币不会被换算。人口为零时不显示人均结果,上一金额为零时不显示变化百分比,因为除以零没有有意义的解释。' },
95
+ { type: 'paragraph', html: '嵌套类别尤其需要注意。如果表格同时把教育和教育 > 学校作为金额,直接相加可能把同一笔支出计算两次。工具会提示嵌套路径,但只有来源方法能够说明父行是小计还是独立项目。' },
96
+ { type: 'title', text: '第一次分析之后要做什么', level: 2 },
97
+ { type: 'paragraph', html: '使用行号回到官方文件,记录每一个修正或解释。比较含义相同的期间,保留负数调整,并在来源确认小计已经包含其中时删除小计后重新分析。' },
98
+ { type: 'tip', title: '保持数据边界清晰', html: '这是本地描述性分析。它不验证文件是否完整,不按官方标准进行支出分类,不协调货币,也不证明支出与结果之间存在因果关系。' },
99
+ ],
100
+ faq: [
101
+ { question: '分析需要哪些列?', answer: '每行需要类别和数字金额。人口、期间、priorAmount、货币和父类别是可选的。' },
102
+ { question: '人均支出如何计算?', answer: '总额除以第一个一致的正人口值。分母不一致时会产生警告。' },
103
+ { question: '重复类别会怎样?', answer: '相同类别路径会相加,同时显示来源行以便检查。' },
104
+ { question: '它能证明预算很好或公平吗?', answer: '不能。它描述提供的数字,不能判断完整性、合法性、效率、公平性或结果。' },
105
+ ],
106
+ bibliography,
107
+ howTo: [
108
+ { name: '准备表格', text: '创建每行包含类别和金额的 CSV 或 JSON,并使用相同货币和会计口径。' },
109
+ { name: '补充背景字段', text: '只有在含义一致时才加入人口、期间、priorAmount、货币或父类别。' },
110
+ { name: '加载文件', text: '拖入文件或加载示例来查看预期结构。' },
111
+ { name: '阅读全景', text: '用比例条查看构成,用表格查看数值、变化和来源行。' },
112
+ { name: '检查警告', text: '下结论前先处理混合货币、重复项、负数调整、零分母和父行。' },
113
+ ],
114
+ schemas: [softwareApplication, faqPage, howTo] as unknown as Record<string, unknown>[],
115
+ };
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { publicBudgetAnalyzer } from './entry';
3
+
4
+ export * from './entry';
5
+ export * from './logic';
6
+ export const PUBLIC_BUDGET_ANALYZER_TOOL: ToolDefinition = {
7
+ entry: publicBudgetAnalyzer,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { analyzeBudgetRecords, parseBudgetText } from './logic';
3
+ import { reviewedCsv } from './dom-views';
4
+
5
+ describe('public budget analyzer logic', () => {
6
+ it('parses CSV with quoted special characters and decimal values', () => {
7
+ const parsed = parseBudgetText('category,amount\n"Parks, east","1.234,50"');
8
+ expect(parsed.errors).toEqual([]);
9
+ expect(parsed.records[0]?.category).toBe('Parks, east');
10
+ expect(parsed.records[0]?.amount).toBe('1.234,50');
11
+ });
12
+
13
+ it('parses JSON arrays and data envelopes', () => {
14
+ expect(parseBudgetText('[{"category":"Health","amount":10}]').records).toHaveLength(1);
15
+ expect(parseBudgetText('{"data":[{"category":"Health","amount":10}]}', 'json').records).toHaveLength(1);
16
+ });
17
+
18
+ it('reports empty and malformed inputs', () => {
19
+ expect(parseBudgetText('').errors).toContain('The file is empty.');
20
+ expect(parseBudgetText('category,amount\nParks', 'csv').errors).toEqual([]);
21
+ expect(parseBudgetText('category,amount\n"Parks,10', 'csv').errors).toContain('CSV contains an unclosed quoted field.');
22
+ expect(parseBudgetText('{bad', 'json').errors).toContain('JSON could not be parsed.');
23
+ expect(parseBudgetText('{"data":[1]}', 'json').errors).toContain('JSON must be an array of objects or an object with a data array.');
24
+ });
25
+
26
+ it('calculates totals, shares, per capita values, and period changes', () => {
27
+ const analysis = analyzeBudgetRecords([
28
+ { category: 'Education', amount: '1,000.50', population: 100, priorAmount: 800, currency: 'EUR', period: '2026' },
29
+ { category: 'Health', amount: 500, population: 100, priorAmount: 600, currency: 'EUR', period: '2026' },
30
+ ]);
31
+ expect(analysis.total).toBe(1500.5);
32
+ expect(analysis.perCapita).toBe(15.005);
33
+ expect(analysis.variation).toBeCloseTo(0.0717857);
34
+ expect(analysis.categories[0]?.share).toBeCloseTo(1000.5 / 1500.5);
35
+ expect(analysis.currency).toBe('EUR');
36
+ expect(analysis.period).toBe('2026');
37
+ });
38
+
39
+ it('groups duplicate paths, retains source rows, and handles hierarchy', () => {
40
+ const analysis = analyzeBudgetRecords([
41
+ { category: 'Education > Schools', amount: 50 },
42
+ { category: 'Schools', parentCategory: 'Education', amount: 25 },
43
+ { category: 'Housing', amount: 10 },
44
+ ]);
45
+ expect(analysis.categories[0]?.amount).toBe(75);
46
+ expect(analysis.categories[0]?.sourceRows).toEqual([2, 3]);
47
+ expect(analysis.warnings.some((warning) => warning.includes('Duplicate category paths'))).toBe(true);
48
+ expect(analysis.warnings.some((warning) => warning.includes('Nested paths'))).toBe(true);
49
+ });
50
+
51
+ it('keeps negative adjustments and warns about zero denominators and baselines', () => {
52
+ const analysis = analyzeBudgetRecords([
53
+ { category: 'Adjustment', amount: -20, population: 0, priorAmount: 0 },
54
+ { category: 'Services', amount: 20, population: 200, priorAmount: 100 },
55
+ ]);
56
+ expect(analysis.total).toBe(0);
57
+ expect(analysis.positiveTotal).toBe(20);
58
+ expect(analysis.perCapita).toBe(0);
59
+ expect(analysis.variation).toBe(-1);
60
+ expect(analysis.warnings.join(' ')).toContain('Negative amounts');
61
+ expect(analysis.warnings.join(' ')).toContain('Population contains a non-positive value');
62
+ expect(analysis.warnings.join(' ')).toContain('total is not positive');
63
+ expect(analysis.warnings.join(' ')).toContain('zero prior amount');
64
+ });
65
+
66
+ it('keeps valid rows while reporting missing categories and amounts', () => {
67
+ const analysis = analyzeBudgetRecords([
68
+ { category: 'Valid', amount: 12 },
69
+ { category: '', amount: 4 },
70
+ { category: 'Missing amount', amount: 'unknown' },
71
+ { category: 'Bad population', amount: 2, population: 'many' },
72
+ ]);
73
+ expect(analysis.rows).toHaveLength(1);
74
+ expect(analysis.errors).toHaveLength(3);
75
+ });
76
+
77
+ it('escapes reviewed CSV values', () => {
78
+ const analysis = analyzeBudgetRecords([{ category: 'Culture, "north"', amount: 12 }]);
79
+ expect(reviewedCsv(analysis)).toContain('"Culture, ""north"""');
80
+ });
81
+ });