TravelContentCreator/docs/TECHNICAL_DEBT.md

937 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 技术债务分析报告
> 本文档总结了 TravelContentCreator 项目中的主要技术债务,为后续重构提供参考。
---
## 1. 数据库双端访问问题
### 现状
Python 端和 Java 端**同时直接访问同一个数据库**,各自维护独立的连接池和查询逻辑。
```
┌─────────────────┐ ┌─────────────────┐
│ Java 后端 │ │ Python AIGC │
│ (zwy_picture) │ │ (TravelContent) │
└────────┬────────┘ └────────┬────────┘
│ │
│ 独立连接池 │ 独立连接池
│ 独立 ORM │ 独立 SQL
▼ ▼
┌─────────────────────────────────┐
│ MySQL (travel_content) │
└─────────────────────────────────┘
```
### 问题
| 问题 | 影响 |
|-----|------|
| **数据一致性风险** | 两端可能读到不同状态的数据 |
| **连接池资源浪费** | 两套连接池,占用更多连接数 |
| **Schema 同步困难** | 表结构变更需要同时修改两端代码 |
| **事务边界模糊** | 跨服务操作无法保证事务一致性 |
| **代码重复** | 相同的查询逻辑在两端各写一遍 |
### 涉及文件
**Python 端:**
- `api/services/database_service.py` - 独立的数据库服务
- `api/services/prompt_service.py` - 也有自己的连接池
- `infrastructure/database/connection.py` - 新架构的连接管理
**Java 端:**
- `ProductPackageService` - 套餐查询
- `MaterialService` - 素材查询
- 各种 Mapper/Repository
### 建议方案
**方案 A: Python 作为纯算法服务**
```
Java 后端 ──HTTP──> Python AIGC
│ │
│ │ (无数据库访问)
▼ │
MySQL <──────────────┘
```
- Python 只接收 Java 传来的完整数据
- 所有数据库操作由 Java 统一管理
- Python 变成无状态的计算服务
**方案 B: 数据库访问层抽象**
- 创建统一的数据访问 API (REST/gRPC)
- 两端通过 API 访问数据,不直接连数据库
---
## 2. 图片 Base64 传输问题
### 现状
Java 端从 S3 下载图片 → 转 Base64 → HTTP 传给 Python → Python 解码 → 处理
```python
# poster.py L266
image_bytes = base64.b64decode(first_image_base64)
```
```java
// PosterGenerateServiceImpl.java L327
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
```
### 问题
| 问题 | 影响 |
|-----|------|
| **带宽浪费** | Base64 编码增加 33% 数据量 |
| **内存压力** | 大图片需要完整加载到内存 |
| **延迟增加** | 编解码耗时 + 传输耗时 |
| **HTTP 请求体过大** | 多图场景可能超过限制 |
| **重复传输** | 同一图片可能被多次传输 |
### 数据示例
```
原始图片: 2MB
Base64 后: 2.67MB (+33%)
HTTP 传输: 2.67MB
Python 解码: 2MB 内存占用
```
### 建议方案
**方案 A: URL 引用模式**
```
Java 端: 上传图片到 S3返回 URL
Python 端: 直接从 S3/CDN 下载
```
**方案 B: 共享存储**
```
Java 端: 保存图片到共享目录 /data/images/{id}.png
Python 端: 直接读取文件路径
```
**方案 C: 图片 ID 引用**
```json
{
"image_ids": [123, 456],
"image_source": "s3"
}
```
Python 端根据 ID 自行获取图片
---
## 3. ppid / sid / pid 混乱问题
### 现状
系统中存在多种 ID 体系,命名不一致,转换逻辑分散:
| 缩写 | 全称 | 说明 |
|-----|------|------|
| `ppid` | Product Package ID | 套餐 ID (Java 端主用) |
| `pid` | Product ID | 产品 ID |
| `sid` | Scenic Spot ID | 景区 ID |
| `scenic_spot_id` | - | Python 端用的景区 ID |
| `product_id` | - | Python 端用的产品 ID |
### 问题
```python
# topic_generate.py - 需要手动解析 ppid
if ppid and (not scenic_spot_id or not product_id):
resolved = await self.db.resolve_ppid(ppid)
scenic_spot_id = resolved.get('scenic_spot_id')
product_id = resolved.get('product_id')
```
```java
// AigcCompatibilityServiceImpl.java - Java 端也要解析
OriginalIds originalIds = getOriginalIdsByPackageId(packageId);
```
| 问题 | 影响 |
|-----|------|
| **命名不一致** | `ppid` vs `packageId` vs `product_package_id` |
| **解析逻辑重复** | Java 和 Python 各自实现一遍 |
| **参数传递混乱** | 有时传 ppid有时传 sid+pid |
| **向后兼容负担** | 需要同时支持多种参数格式 |
### 涉及文件
- `domain/aigc/engines/*.py` - 每个引擎都要处理 ppid
- `infrastructure/database/repositories/product_package_repository.py`
- `AigcCompatibilityServiceImpl.java`
### 建议方案
**统一入口点:**
```
前端/调用方 ──ppid──> Java 后端 ──解析──> sid + pid ──> Python
```
- 所有 ppid 解析在 Java 端完成
- Python 端只接收已解析的 `scenic_spot_id``product_id`
- 消除 Python 端的 ppid 解析逻辑
---
## 4. Prompt 拼接方式问题
### 现状
Prompt 构建分散在多个地方,使用字符串拼接和模板文件混合:
```python
# prompt_builder.py L84-90
user_prompt = template.build_user_prompt(
style_content=style_content,
demand_content=demand_content,
object_content=object_content,
product_content=product_content,
refer_content=refer_content
)
```
```python
# prompts.py L64-72
def build_user_prompt(self, **kwargs) -> str:
return self.user_template.format(**kwargs)
```
### 问题
| 问题 | 影响 |
|-----|------|
| **模板文件分散** | 模板在 `prompts/` 目录,配置在 `config/` |
| **变量命名不统一** | `style_content` vs `style` vs `styleName` |
| **难以调试** | 最终 prompt 难以追踪 |
| **版本管理困难** | prompt 变更难以追踪和回滚 |
| **缺乏验证** | 缺少 prompt 有效性检查 |
### 涉及文件
- `utils/prompts.py` - 基础模板类
- `api/services/prompt_builder.py` - 内容 prompt 构建
- `api/services/prompt_service.py` - prompt 服务
- `prompts/*.txt` - 模板文件
### 建议方案
**方案 A: 集中式 Prompt 管理**
```python
class PromptRegistry:
def get_prompt(self, name: str, version: str = "latest") -> PromptTemplate:
"""从数据库或配置中心获取 prompt"""
pass
def render(self, name: str, context: dict) -> str:
"""渲染 prompt"""
pass
```
**方案 B: Prompt 配置化**
```yaml
# prompts.yaml
topic_generate:
version: "1.2.0"
system: |
你是一个旅游营销专家...
user: |
请为 {scenic_spot} 生成 {num_topics} 个选题...
variables:
- scenic_spot: required
- num_topics: default=5
```
---
## 5. 工具使用方式问题
### 现状
各种工具类和服务的使用方式不统一:
```python
# 方式1: 全局单例
from api.dependencies import get_ai_agent, get_config
# 方式2: 构造函数注入
class TopicGenerator:
def __init__(self, ai_agent: AIAgent, config_manager: ConfigManager):
# 方式3: 延迟加载
def _get_tweet_service(self):
if self._tweet_service:
return self._tweet_service
from api.services.tweet import TweetService
self._tweet_service = TweetService(...)
# 方式4: 直接实例化
db_service = DatabaseService(config_manager)
```
### 问题
| 问题 | 影响 |
|-----|------|
| **依赖注入不一致** | 有的用单例,有的用构造函数 |
| **循环导入风险** | 延迟导入是为了避免循环依赖 |
| **测试困难** | 难以 mock 依赖 |
| **生命周期不清晰** | 不知道对象何时创建/销毁 |
| **资源泄漏风险** | 多个实例可能创建多个连接池 |
### 涉及文件
- `api/dependencies.py` - 依赖获取
- `domain/aigc/shared/component_factory.py` - 组件工厂
- 各个 Engine 类的 `_get_xxx_service()` 方法
### 建议方案
**统一依赖注入容器:**
```python
class Container:
_instances = {}
@classmethod
def get(cls, service_class: Type[T]) -> T:
if service_class not in cls._instances:
cls._instances[service_class] = cls._create(service_class)
return cls._instances[service_class]
@classmethod
def _create(cls, service_class):
# 根据类型创建实例,自动解析依赖
pass
```
**或使用现有框架:**
- `dependency-injector`
- FastAPI 的 `Depends` 系统
---
## 总结:优先级建议
| 优先级 | 问题 | 原因 |
|-------|------|------|
| 🔴 高 | 数据库双端访问 | 数据一致性风险最大 |
| 🔴 高 | ppid 混乱 | 影响所有 AIGC 调用 |
| 🟡 中 | 图片 Base64 传输 | 性能问题,但功能正常 |
| 🟡 中 | Prompt 拼接 | 维护困难,但不影响功能 |
| 🟢 低 | 工具使用方式 | 代码质量问题,可渐进改进 |
---
---
## 6. 临时文件堆积问题
### 现状
每次 API 请求都会在 `result/` 目录下创建新的子目录,保存中间产物:
```bash
$ du -sh result/
943M result/
$ ls -1 result/ | wc -l
6931
```
**6931 个目录,占用 943MB 磁盘空间!**
### 问题
| 问题 | 影响 |
|-----|------|
| **磁盘空间浪费** | 近 1GB 的临时文件 |
| **无清理机制** | 文件只增不减 |
| **目录数量过多** | 影响文件系统性能 |
| **敏感信息泄露风险** | prompt、生成内容可能包含敏感数据 |
### 涉及文件
- `utils/file_io.py` - `OutputManager`
- 每次请求创建: `result/api_request-{timestamp}-{uuid}/`
### 建议方案
1. **定期清理**: 添加定时任务清理 7 天前的目录
2. **按需保存**: 只在调试模式下保存中间产物
3. **内存缓存**: 中间结果保存在内存,不落盘
4. **统一存储**: 重要结果上传到 S3本地只做临时缓存
---
## 7. 配置分散问题
### 现状
配置文件分散在多个 JSON 文件中:
```
config/
├── ai_model.json # AI 模型配置
├── content_gen.json # 内容生成配置 (含 prompt 路径)
├── topic_gen.json # 选题生成配置 (含 prompt 路径)
├── poster_gen.json # 海报生成配置
├── database.json # 数据库配置
├── paths.json # 路径配置
├── resource.json # 资源配置
├── system.json # 系统配置
└── cookies.json # Cookie 配置
```
### 问题
| 问题 | 影响 |
|-----|------|
| **配置分散** | 9 个配置文件,难以统一管理 |
| **路径嵌套** | 配置中引用其他配置路径 |
| **环境切换困难** | 没有 dev/prod 环境区分 |
| **敏感信息明文** | 数据库密码、API Key 明文存储 |
### 建议方案
1. **环境变量优先**: 敏感信息从环境变量读取
2. **配置合并**: 合并为 `config.yaml` + 环境覆盖
3. **配置中心**: 使用 Consul/Nacos 等配置中心
---
## 8. 错误处理不一致
### 现状
不同模块的错误处理方式不统一:
```python
# 方式1: 返回 None
if not topics:
return None
# 方式2: 返回错误字典
return {"error": str(e)}
# 方式3: 抛出异常
raise ValueError(f"生成海报失败: {str(e)}")
# 方式4: 返回元组
return str(uuid.uuid4()), []
```
### 问题
| 问题 | 影响 |
|-----|------|
| **调用方难以处理** | 需要检查多种错误格式 |
| **错误信息丢失** | 有些地方只返回 None |
| **日志不统一** | 有的记录,有的不记录 |
| **没有错误码** | 难以区分错误类型 |
### 建议方案
统一使用 `Result` 模式:
```python
@dataclass
class Result(Generic[T]):
success: bool
data: Optional[T] = None
error: Optional[str] = None
error_code: Optional[str] = None
```
---
## 4. Prompt 管理方案 (详细设计)
### 当前问题分析
```
config/content_gen.json
└── "content_system_prompt": "resource/prompt/generateContent/system.txt"
resource/prompt/generateContent/system.txt (87 行硬编码的 prompt)
utils/prompts.py PromptTemplate.format(**kwargs)
api/services/prompt_builder.py 拼接变量
```
**问题:**
1. Prompt 内容硬编码在 `.txt` 文件中
2. 变量占位符 `{style_content}` 与代码强耦合
3. 无版本管理,修改后无法回滚
4. 无 A/B 测试能力
5. 无效果追踪
### 推荐方案: YAML + 版本化 Prompt Registry
#### 目录结构
```
prompts/
├── registry.yaml # Prompt 注册表
├── topic_generate/
│ ├── v1.0.0.yaml # 版本化的 prompt
│ ├── v1.1.0.yaml
│ └── latest -> v1.1.0.yaml # 软链接
├── content_generate/
│ ├── v1.0.0.yaml
│ └── latest -> v1.0.0.yaml
└── content_judge/
└── v1.0.0.yaml
```
#### Prompt 定义格式 (YAML)
```yaml
# prompts/content_generate/v1.0.0.yaml
meta:
name: content_generate
version: "1.0.0"
description: "小红书风格内容生成"
author: "team"
created_at: "2024-12-08"
# 模型参数
model:
temperature: 0.3
top_p: 0.5
presence_penalty: 1.2
# 变量定义 (带类型和默认值)
variables:
style_content:
type: string
required: true
description: "风格描述"
demand_content:
type: string
required: true
description: "受众需求"
object_content:
type: string
required: true
description: "景区/产品信息"
product_content:
type: string
required: false
default: ""
description: "产品详情"
refer_content:
type: string
required: false
default: ""
description: "参考范文"
# System Prompt
system: |
你是景区小红书爆款文案策划,你将根据要求创作爆款文案。
## 标题创作规则
1. 必加1个emoji标题字数18字以内
2. 有网感,结合所在地
3. 标题必须结合给出的标题参考格式进行高相似度仿写
## 正文创作规则
1. 以"你"这种有人味的人称代词视角创作
2. 不要出现"宝子们""姐妹们"这些很假的称呼
3. 直击用户痛点,有场景感
4. 分段+分点论述巧用emoji
## 输出格式
```json
{
"title": "标题内容",
"content": "正文内容",
"tag": "#标签1 #标签2 ..."
}
```
# User Prompt (使用 Jinja2 模板语法)
user: |
请根据以下材料创作一篇小红书文案:
【风格要求】
{{ style_content }}
【目标受众】
{{ demand_content }}
【景区/产品信息】
{{ object_content }}
{% if product_content %}
【产品详情】
{{ product_content }}
{% endif %}
{% if refer_content %}
【参考范文】
{{ refer_content }}
{% endif %}
```
#### Prompt Registry 实现
```python
# domain/prompt/registry.py
from pathlib import Path
from typing import Dict, Any, Optional
import yaml
from jinja2 import Template
from dataclasses import dataclass
@dataclass
class PromptConfig:
"""Prompt 配置"""
name: str
version: str
system: str
user: str
variables: Dict[str, Any]
model: Dict[str, float]
class PromptRegistry:
"""
Prompt 注册表
功能:
1. 加载和缓存 prompt 配置
2. 版本管理 (latest, v1.0.0, v1.1.0)
3. 变量验证
4. 模板渲染
"""
def __init__(self, prompts_dir: str = "prompts"):
self.prompts_dir = Path(prompts_dir)
self._cache: Dict[str, PromptConfig] = {}
def get(self, name: str, version: str = "latest") -> PromptConfig:
"""
获取 prompt 配置
Args:
name: prompt 名称 (如 "content_generate")
version: 版本号 (如 "v1.0.0" 或 "latest")
"""
cache_key = f"{name}:{version}"
if cache_key not in self._cache:
self._cache[cache_key] = self._load(name, version)
return self._cache[cache_key]
def render(self, name: str, context: Dict[str, Any],
version: str = "latest") -> tuple[str, str]:
"""
渲染 prompt
Returns:
(system_prompt, user_prompt)
"""
config = self.get(name, version)
# 验证必填变量
self._validate_variables(config, context)
# 渲染模板
system = config.system # system 通常不需要变量
user = Template(config.user).render(**context)
return system, user
def _load(self, name: str, version: str) -> PromptConfig:
"""加载 prompt 配置文件"""
if version == "latest":
# 读取 latest 软链接指向的文件
prompt_path = self.prompts_dir / name / "latest.yaml"
if prompt_path.is_symlink():
prompt_path = prompt_path.resolve()
elif not prompt_path.exists():
# 找最新版本
versions = sorted(
(self.prompts_dir / name).glob("v*.yaml"),
reverse=True
)
if versions:
prompt_path = versions[0]
else:
prompt_path = self.prompts_dir / name / f"{version}.yaml"
if not prompt_path.exists():
raise FileNotFoundError(f"Prompt not found: {name}:{version}")
with open(prompt_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
return PromptConfig(
name=data['meta']['name'],
version=data['meta']['version'],
system=data['system'],
user=data['user'],
variables=data.get('variables', {}),
model=data.get('model', {})
)
def _validate_variables(self, config: PromptConfig, context: Dict[str, Any]):
"""验证变量"""
for var_name, var_config in config.variables.items():
if var_config.get('required', False) and var_name not in context:
raise ValueError(f"Missing required variable: {var_name}")
def list_versions(self, name: str) -> list[str]:
"""列出所有版本"""
prompt_dir = self.prompts_dir / name
if not prompt_dir.exists():
return []
versions = []
for f in prompt_dir.glob("v*.yaml"):
versions.append(f.stem)
return sorted(versions, reverse=True)
```
#### 使用示例
```python
# 使用新的 Prompt Registry
registry = PromptRegistry("prompts")
# 渲染 prompt
system, user = registry.render(
name="content_generate",
context={
"style_content": "攻略风格,实用性强",
"demand_content": "亲子家庭,周末出游",
"object_content": "天津冒险湾主题乐园...",
"product_content": "门票299元/人",
},
version="latest" # 或指定 "v1.0.0"
)
# 获取模型参数
config = registry.get("content_generate")
model_params = config.model # {"temperature": 0.3, ...}
```
#### 优势
| 特性 | 说明 |
|-----|------|
| **版本管理** | 每个版本独立文件,可回滚 |
| **变量验证** | 自动检查必填变量 |
| **模板语法** | Jinja2 支持条件、循环 |
| **模型参数绑定** | prompt 和模型参数一起管理 |
| **缓存** | 加载后缓存,避免重复 IO |
| **A/B 测试** | 可指定不同版本测试效果 |
---
## 下一步行动
### 已确认的改进方向
| # | 问题 | 方案 |
|---|------|------|
| 1 | 数据库双端访问 | Python 不访问数据库,改为接口传输 |
| 2 | 图片 Base64 传输 | 改为 URL/路径引用 |
| 3 | ppid 混乱 | 放弃 ppidJava 直接传完整数据 |
| 4 | Prompt 管理 | YAML + 版本化 Registry |
| 5 | 依赖注入 | 统一容器模式 |
| 6 | 临时文件堆积 | 添加清理机制 |
| 7 | 配置分散 | 合并 + 环境变量 |
| 8 | 错误处理不一致 | 统一 Result 模式 |
### 优先级排序
1. **🔴 高优先级** (影响架构)
- 数据库访问改接口
- 图片传输改 URL
- 放弃 ppid
2. **🟡 中优先级** (影响维护)
- Prompt Registry
- 错误处理统一
- 临时文件清理
3. **🟢 低优先级** (代码质量)
- 依赖注入优化
- 配置合并
---
## 9. 巨型文件问题
### 现状
部分文件代码量过大,难以维护:
| 文件 | 行数 | 问题 |
|-----|------|------|
| `demo_refactored_templates.py` | 4219 | 示例/废弃代码? |
| `poster_template.py` | 3421 | 根目录下的模板文件 |
| `api/services/poster.py` | 3031 | 海报服务,职责过多 |
| `poster/templates/vibrant_template.py` | 1756 | 单个模板文件过大 |
| `api/services/database_service.py` | 1054 | 数据库服务 |
| `core/xhs_spider/apis/xhs_pc_apis.py` | 1019 | 小红书 API |
### 问题
| 问题 | 影响 |
|-----|------|
| **难以理解** | 3000+ 行代码难以阅读 |
| **难以测试** | 职责混杂,难以单元测试 |
| **合并冲突** | 多人修改同一文件容易冲突 |
| **废弃代码** | 根目录下的 `poster_template.py``demo_*.py` 可能是废弃代码 |
### 建议方案
1. **拆分大文件**: `poster.py` 拆分为多个模块 (已在新架构中完成)
2. **清理废弃代码**: 删除根目录下的 `poster_template.py`, `demo_*.py`
3. **单一职责**: 每个类/模块只做一件事
---
## 10. 重复代码问题
### 现状
存在多处重复或相似的代码:
```
# 目录重复
/root/TravelContentCreator/document/ # 旧位置
/root/TravelContentCreator/core/document/ # 新位置 (重复)
# 文件重复
./document/content_transformer.py
./core/document/content_transformer.py
./document/content_integrator.py
./core/document/content_integrator.py
```
### 问题
| 问题 | 影响 |
|-----|------|
| **不知道用哪个** | 两个位置都有相同文件 |
| **修改遗漏** | 改了一个忘了另一个 |
| **导入混乱** | `from document.xxx` vs `from core.document.xxx` |
### 建议方案
1. 确定唯一位置
2. 删除重复目录
3. 更新所有 import
---
## 11. API 路由分散问题
### 现状
API 路由分散在多个文件中,功能有重叠:
```
api/routers/
├── aigc.py # 新的统一 AIGC API
├── tweet.py # 旧的选题/内容 API (470 行)
├── poster.py # 旧的海报 API
├── data.py # 数据查询 API
├── document.py # 文档处理 API
├── integration.py # 集成 API
├── content_integration.py # 内容集成 API
└── prompt.py # 提示词 API
```
### 问题
| 问题 | 影响 |
|-----|------|
| **功能重叠** | `aigc.py``tweet.py`/`poster.py` 功能重复 |
| **命名不一致** | `tweet` vs `content`, `integration` vs `content_integration` |
| **API 版本混乱** | 没有清晰的 v1/v2 区分 |
### 建议方案
1. 统一使用 `/api/v2/aigc/*` 作为新入口
2. 旧 API 标记为 deprecated
3. 逐步迁移后删除旧路由
---
## 12. 日志配置问题
### 现状
每个文件都单独配置 logger
```python
# 几乎每个 .py 文件都有这行
logger = logging.getLogger(__name__)
```
但没有统一的日志配置:
- 日志格式不统一
- 日志级别分散控制
- 没有日志轮转
- 没有结构化日志
### 建议方案
```python
# 统一日志配置
LOGGING_CONFIG = {
"version": 1,
"formatters": {
"default": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"json": {
"class": "pythonjsonlogger.jsonlogger.JsonFormatter"
}
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "default"},
"file": {"class": "logging.handlers.RotatingFileHandler", ...}
},
"root": {"level": "INFO", "handlers": ["console", "file"]}
}
```
---
## 完整问题清单
| # | 问题 | 严重程度 | 状态 |
|---|------|---------|------|
| 1 | 数据库双端访问 | 🔴 高 | ✅ V2 引擎已解决 |
| 2 | 图片 Base64 传输 | 🟡 中 | ✅ V2 引擎支持 URL |
| 3 | ppid 混乱 | 🔴 高 | ✅ 已废弃Java 端传完整对象 |
| 4 | Prompt 管理分散 | 🟡 中 | ✅ PromptRegistry 已实现 |
| 5 | 依赖注入不统一 | 🟢 低 | ✅ Container 已实现 |
| 6 | 临时文件堆积 | 🟡 中 | ✅ 清理脚本已创建 |
| 7 | 配置分散 | 🟢 低 | ✅ UnifiedConfig 已实现 |
| 8 | 错误处理不一致 | 🟡 中 | ✅ 统一异常类已定义 |
| 9 | 巨型文件 | 🟡 中 | 部分已拆分 |
| 10 | 重复代码/目录 | 🟡 中 | ✅ document 已统一 |
| 11 | API 路由分散 | 🟢 低 | 新架构已统一 |
| 12 | 日志配置缺失 | 🟢 低 | ✅ logging_config 已实现 |
> 详细实施记录见 [REFACTORING_IMPLEMENTATION.md](./REFACTORING_IMPLEMENTATION.md)