# All files / modules/book-generator book-type-config.ts

100% Statements 89/89
100% Branches 15/15
100% Functions 12/12
100% Lines 89/89

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154

    1x

    1x
    4x
    4x

    1x

    1x
    4x
    4x
    4x
    4x
    4x

    1x
    1x
    60x
    60x

    1x
    3x
    3x
    3x
    3x
    3x

    1x

    1x
    47x
    47x

    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x

    1x

    1x

    14x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    13x
    1x
    14x

    1x
    13x
    13x

    1x
    2x
    2x

    1x
    2x
    2x

    1x
    2x
    2x
    2x
    2x
    2x
    2x
    2x
    2x

    1x
    4x
    4x
    4x
    4x
    4x
    4x
    4x

    1x

    1x
    1x
    1x
     /**
     * 书籍类型配置（黄金数 φ=1.618，基数13万）
     * 规模值为精确字数，AI 生成时自行理解 ±20% 浮动
     */

    // 通用浮动比例（可调整）
    export const FLOAT_RATIO = 0.2; // 20%

    // AI 生成时字数上限（基数 × 1.2）
    export const getWordUpperLimit = (baseWords: number): number => {
      return Math.round(baseWords * (1 + FLOAT_RATIO));
    };

    // 每章字数浮动比例（可调整）
    export const CHAPTER_WORDS_FLOAT_RATIO = 0.5; // 50%

    // 章节数浮动范围（根据 FLOAT_RATIO 计算，允许 ±20%）
    export const getChapterRange = (targetChapters: number): { min: number; max: number } => {
      return {
        min: Math.floor(targetChapters * (1 - FLOAT_RATIO)),
        max: Math.ceil(targetChapters * (1 + FLOAT_RATIO)),
      };
    };

    // 根据总字数计算章节数（每章约 2千 字，可调整）
    const WORDS_PER_CHAPTER = 2000;
    export const calcChapters = (totalWords: number): number => {
      return Math.max(1, Math.round(totalWords / WORDS_PER_CHAPTER));
    };

    // 每章字数浮动范围
    export const getChapterWordsRange = (baseWords: number = WORDS_PER_CHAPTER): { min: number; max: number } => {
      return {
        min: Math.round(baseWords * (1 - CHAPTER_WORDS_FLOAT_RATIO)),
        max: Math.round(baseWords * (1 + CHAPTER_WORDS_FLOAT_RATIO)),
      };
    };

    // 每分钟音频对应的文本字数（可调整）
    const WORDS_PER_MINUTE = 200;

    // 根据总字数计算音频时长（分钟）
    export const calcAudioMinutes = (totalWords: number): number => {
      return Math.round(totalWords / WORDS_PER_MINUTE);
    };

    export interface BookTypeConfig {
      key: string;
      label: string;
      description: string;
      chapters: number;
      totalWords: number;
      chapterWords: number;
      audioMinutes: number;
      structureFormat: string;
      readingDifficulty: string;
      isShortArticle: boolean;
    }

    // 音频分钟数计算：totalWords / 200
    export const BOOK_TYPE_CONFIG: Record<string, BookTypeConfig> = {
      '340000': { key: '340000', label: '上2', description: '上2，约34万字', chapters: calcChapters(340000), totalWords: 340000, chapterWords: 9700, audioMinutes: calcAudioMinutes(340000), structureFormat: '单章完整', readingDifficulty: '高', isShortArticle: false },
      '210000': { key: '210000', label: '上1', description: '上1，约21万字', chapters: calcChapters(210000), totalWords: 210000, chapterWords: 7800, audioMinutes: calcAudioMinutes(210000), structureFormat: '单章完整', readingDifficulty: '中高', isShortArticle: false },
      '130000': { key: '130000', label: '标准', description: '标准，约13万字', chapters: calcChapters(130000), totalWords: 130000, chapterWords: 7600, audioMinutes: calcAudioMinutes(130000), structureFormat: '单章完整', readingDifficulty: '中', isShortArticle: false },
      '80000': { key: '80000', label: '下1', description: '下1，约8万字', chapters: calcChapters(80000), totalWords: 80000, chapterWords: 6700, audioMinutes: calcAudioMinutes(80000), structureFormat: '单章完整', readingDifficulty: '中低', isShortArticle: false },
      '50000': { key: '50000', label: '下2', description: '下2，约5万字', chapters: calcChapters(50000), totalWords: 50000, chapterWords: 5600, audioMinutes: calcAudioMinutes(50000), structureFormat: '单章完整', readingDifficulty: '低', isShortArticle: false },
      '31000': { key: '31000', label: '下3', description: '下3，约3.1万字', chapters: calcChapters(31000), totalWords: 31000, chapterWords: 4400, audioMinutes: calcAudioMinutes(31000), structureFormat: '单章完整', readingDifficulty: '低', isShortArticle: false },
      '19000': { key: '19000', label: '下4', description: '下4，约1.9万字', chapters: calcChapters(19000), totalWords: 19000, chapterWords: 3800, audioMinutes: calcAudioMinutes(19000), structureFormat: '单章完整', readingDifficulty: '低', isShortArticle: false },
      '12000': { key: '12000', label: '下5', description: '下5，约1.2万字', chapters: calcChapters(12000), totalWords: 12000, chapterWords: 3000, audioMinutes: calcAudioMinutes(12000), structureFormat: '单章完整', readingDifficulty: '低', isShortArticle: false },
      '7000': { key: '7000', label: '下6', description: '下6，约7千字', chapters: calcChapters(7000), totalWords: 7000, chapterWords: 2300, audioMinutes: calcAudioMinutes(7000), structureFormat: '全文', readingDifficulty: '极低', isShortArticle: true },
      '4000': { key: '4000', label: '下7', description: '下7，约4千字', chapters: calcChapters(4000), totalWords: 4000, chapterWords: 2000, audioMinutes: calcAudioMinutes(4000), structureFormat: '全文', readingDifficulty: '极低', isShortArticle: true },
      '3000': { key: '3000', label: '下8', description: '下8，约3千字', chapters: calcChapters(3000), totalWords: 3000, chapterWords: 3000, audioMinutes: calcAudioMinutes(3000), structureFormat: '全文', readingDifficulty: '极低', isShortArticle: true },
      '2000': { key: '2000', label: '下9', description: '下9，约2千字', chapters: calcChapters(2000), totalWords: 2000, chapterWords: 2000, audioMinutes: calcAudioMinutes(2000), structureFormat: '全文', readingDifficulty: '极低', isShortArticle: true },
      '1000': { key: '1000', label: '下10', description: '下10，约1千字', chapters: calcChapters(1000), totalWords: 1000, chapterWords: 1000, audioMinutes: calcAudioMinutes(1000), structureFormat: '全文', readingDifficulty: '极低', isShortArticle: true },
    };

    // 可被 AI 自动检测的类型
    export const DETECTABLE_TYPES = ['340000', '210000', '130000', '80000', '50000', '31000', '19000', '12000', '7000', '4000', '3000', '2000', '1000'];

    // 获取单个类型配置
    export function getBookTypeConfig(key: string): BookTypeConfig {
      // 如果 key 是数字字符串（如 '200', '3000'），直接计算
      if (/^\d+$/.test(key)) {
        const wordCount = parseInt(key);
        return {
          key: key,
          label: '自定义',
          description: `约${wordCount}字`,
          chapters: calcChapters(wordCount),
          totalWords: wordCount,
          chapterWords: calcChapterWords(wordCount),
          audioMinutes: calcAudioMinutes(wordCount),
          structureFormat: '单章完整',
          readingDifficulty: '中',
          isShortArticle: wordCount <= 10000,
        };
      }
      return BOOK_TYPE_CONFIG[key] || BOOK_TYPE_CONFIG['130000'];
    }

    // 根据字数计算每章字数
    const calcChapterWords = (totalWords: number): number => {
      return Math.round(totalWords / calcChapters(totalWords));
    };

    // 获取所有类型配置（供前端显示）
    export function getAllBookTypes(): BookTypeConfig[] {
      return Object.values(BOOK_TYPE_CONFIG);
    }

    // 获取可检测的类型列表
    export function getDetectableTypes(): BookTypeConfig[] {
      return DETECTABLE_TYPES.map(key => BOOK_TYPE_CONFIG[key]);
    }

    // 兼容旧接口的 getScaleConfig（返回单个值，AI自行理解±20%浮动）
    export function getScaleConfig(key: string) {
      const config = getBookTypeConfig(key);
      return {
        chapters: config.chapters,
        totalWords: config.totalWords,
        chapterWords: config.chapterWords,
        isShortArticle: config.isShortArticle,
      };
    }

    // 兼容旧接口的 estimateBookWords
    export function estimateBookWords(key: string): { min: number; max: number; avg: number } {
      const config = getBookTypeConfig(key);
      return {
        min: config.totalWords,
        max: config.totalWords,
        avg: config.totalWords,
      };
    }

    // ============================================
    // 字数安全限制（三层防护体系）
    // ============================================

    /** 全局绝对上限：50 万字
     * 前端最大可选为「上2」34 万字，×1.5 安全系数 = 51 万，取整 50 万
     * 任何用户/任何类型均不可突破 */
    export const GLOBAL_ABSOLUTE_WORD_LIMIT = 500_000;

    /**
     * 获取全局字数上限
     * 与类型/会员等级无关，所有用户统一硬上限 50 万字
     * 用户有多少余额就能写多少字，但不得超过此上限
     */
    export function getBookWordLimit(): number {
      return GLOBAL_ABSOLUTE_WORD_LIMIT;
    }