★★★★★
#앱별 실전 패턴 · claude-code
[buildit] 의뢰자 ↔ 시공자 매칭
빌드잇: 건설 의뢰자(클라이언트)와 시공자 간 프로젝트 매칭.
#buildit#matching#project-management
빌드잇: 건설 의뢰자(클라이언트)와 시공자 간 프로젝트 매칭.
Firestore 스키마:
```
users/{userId}
- role: "client" | "contractor"
- skills: ["electrical", "plumbing", ...] (시공자용)
- serviceArea: ["Seoul", "Busan", ...] (시공자용)
- rating: 4.5
- reviews: 42
projects/{projectId}
- clientId
- title
- description
- requiredSkills: ["electrical", "plumbing"]
- location
- budget: 5000000
- status: "open" | "matched" | "in_progress" | "completed"
- contractorId: (선택사항)
```
매칭 알고리즘:
```dart
class MatchingService {
static Future<List<Contractor>> findMatches(String projectId) async {
final projectDoc = await db.collection('projects').doc(projectId).get();
final project = Project.fromDoc(projectDoc);
// 1. 필요한 스킬 보유 확인
var query = db.collection('users')
.where('role', isEqualTo: 'contractor');
final candidates = await query.get();
// 2. 클라이언트에서 추가 필터링
final matches = <Contractor>[];
for (final doc in candidates.docs) {
final contractor = Contractor.fromDoc(doc);
// 스킬 확인
final hasRequiredSkills = project.requiredSkills
.every((skill) => contractor.skills.contains(skill));
if (!hasRequiredSkills) continue;
// 지역 확인
if (!contractor.serviceArea.contains(project.location)) continue;
// 예산 확인 (선택)
if (contractor.minBudget != null && contractor.minBudget! > project.budget) continue;
// 평점 필터 (선택)
if (contractor.rating < 4.0) continue;
matches.add(contractor);
}
// 평점으로 정렬
matches.sort((a, b) => b.rating.compareTo(a.rating));
return matches.take(10).toList(); // 상위 10명
}
}
```
UI:
```dart
class MatchedContractorsScreen extends StatefulWidget {
final String projectId;
@override
State<MatchedContractorsScreen> createState() => _MatchedContractorsScreenState();
}
class _MatchedContractorsScreenState extends State<MatchedContractorsScreen> {
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Contractor>>(
future: MatchingService.findMatches(widget.projectId),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final contractor = snapshot.data![index];
return ContractorCard(
contractor: contractor,
onTap: () {
// 시공자 상세보기 또는 채팅 시작
},
);
},
);
}
return LoadingWidget();
},
);
}
}
```
구현해줄까?
쓰는 법 — 위 칸에 본인 값을 채우면 아래 프롬프트에 바로 반영돼요. 복사 버튼을 누르면 채워진 그대로 클립보드에 담기니까, claude-code(또는 본인이 쓰는 AI)에 붙여넣기만 하면 됩니다. 변수가 없으면 그냥 복사해서 쓰세요.

