| name | rfi-management |
| description | Complete RFI (Request for Information) management system. Create, track, route, and analyze RFIs with automatic notifications and response deadline tracking. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"📋","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":["python3"]}}} |
RFI Management System for Construction
Comprehensive system for managing Requests for Information (RFIs) throughout the construction project lifecycle.
Business Case
Problem: RFI management is chaotic:
- RFIs get lost in email threads
- Response deadlines missed
- No visibility into RFI status
- Difficult to track cost/schedule impacts
- Manual logging wastes hours weekly
Solution: Structured RFI management that:
- Auto-assigns RFI numbers
- Routes to correct parties
- Tracks response deadlines
- Sends automatic reminders
- Maintains audit trail
- Analyzes trends and impacts
ROI: 60% faster RFI response time, 90% reduction in lost RFIs
RFI Workflow
┌──────────────────────────────────────────────────────────────────────┐
│ RFI LIFECYCLE │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ CREATE │───►│ SUBMIT │───►│ REVIEW │───►│ RESPOND │ │
│ │ │ │ │ │ │ │ │ │
│ │ • Draft │ │ • Route │ │ • Assign│ │ • Answer│ │
│ │ • Attach│ │ • Notify│ │ • Track │ │ • Approve│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ RFI DATABASE │ │
│ │ • RFI Log • Attachments • Response History │ │
│ │ • Status Track • Cost Impacts • Schedule Impacts │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ CLOSE │◄───│ VERIFY │◄───│IMPLEMENT│ │
│ │ │ │ │ │ │ │
│ │ • Archive│ │ • Check │ │ • Action│ │
│ │ • Report│ │ • Accept│ │ • Update│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
Data Structure
RFI Log Schema
RFI_SCHEMA = {
'rfi_number': str,
'project_id': str,
'revision': int,
'subject': str,
'question': str,
'spec_section': str,
'drawing_ref': str,
'location': str,
'submitted_by': str,
'submitted_by_company': str,
'assigned_to': str,
'cc_list': list,
'date_submitted': date,
'date_required': date,
'date_responded': date,
'date_closed': date,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
}
Python Implementation
import pandas as pd
from datetime import datetime, date, timedelta
from typing import Optional, List, Dict
from dataclasses import dataclass, field
from enum import Enum
import uuid
class RFIStatus(Enum):
DRAFT = "Draft"
OPEN = "Open"
PENDING = "Pending Review"
ANSWERED = "Answered"
CLOSED = "Closed"
VOID = "Void"
class RFIPriority(Enum):
CRITICAL = "Critical"
HIGH = "High"
MEDIUM = "Medium"
LOW = "Low"
@dataclass
class RFI:
"""Request for Information data class"""
rfi_number: str
project_id: str
subject: str
question: str
spec_section: str = ""
drawing_ref: str = ""
location: str = ""
submitted_by: =
submitted_by_company: =
assigned_to: =
cc_list: [] = field(default_factory=)
date_submitted: date = field(default_factory=date.today)
date_required: date =
date_responded: date =
date_closed: date =
status: RFIStatus = RFIStatus.DRAFT
priority: RFIPriority = RFIPriority.MEDIUM
response: =
response_by: =
attachments: [] = field(default_factory=)
cost_impact: =
cost_amount: =
schedule_impact: =
schedule_days: =
change_order_ref: =
revision: =
():
.date_required :
.date_required = .date_submitted + timedelta(days=)
:
():
.project_id = project_id
.storage_path = storage_path
.rfis: [, RFI] = {}
._load_rfis()
():
:
df = pd.read_excel(.storage_path)
_, row df.iterrows():
rfi = RFI(
rfi_number=row[],
project_id=row[],
subject=row[],
question=row[],
status=RFIStatus(row[]),
priority=RFIPriority(row.get(, ))
)
.rfis[rfi.rfi_number] = rfi
FileNotFoundError:
():
records = []
rfi .rfis.values():
records.append({
: rfi.rfi_number,
: rfi.project_id,
: rfi.subject,
: rfi.question,
: rfi.spec_section,
: rfi.drawing_ref,
: rfi.location,
: rfi.submitted_by,
: rfi.submitted_by_company,
: rfi.assigned_to,
: rfi.date_submitted,
: rfi.date_required,
: rfi.date_responded,
: rfi.date_closed,
: rfi.status.value,
: rfi.priority.value,
: rfi.response,
: rfi.response_by,
: rfi.cost_impact,
: rfi.cost_amount,
: rfi.schedule_impact,
: rfi.schedule_days,
: rfi.change_order_ref
})
df = pd.DataFrame(records)
df.to_excel(.storage_path, index=)
() -> :
existing = [(r.rfi_number.split()[])
r .rfis.values()
r.rfi_number.startswith()]
next_num = (existing, default=) +
() -> RFI:
rfi_number = ._get_next_number()
rfi = RFI(
rfi_number=rfi_number,
project_id=.project_id,
subject=subject,
question=question,
spec_section=spec_section,
drawing_ref=drawing_ref,
location=location,
submitted_by=submitted_by,
submitted_by_company=submitted_by_company,
assigned_to=assigned_to,
priority=priority,
date_required=date.today() + timedelta(days=days_for_response),
attachments=attachments []
)
.rfis[rfi_number] = rfi
._save_rfis()
rfi
() -> RFI:
rfi = .rfis.get(rfi_number)
rfi:
ValueError()
rfi.status != RFIStatus.DRAFT:
ValueError()
rfi.status = RFIStatus.OPEN
rfi.date_submitted = date.today()
._save_rfis()
._notify_submission(rfi)
rfi
() -> RFI:
rfi = .rfis.get(rfi_number)
rfi:
ValueError()
rfi.response = response
rfi.response_by = response_by
rfi.date_responded = date.today()
rfi.status = RFIStatus.ANSWERED
attachments:
rfi.attachments.extend(attachments)
rfi.cost_impact = cost_impact
rfi.cost_amount = cost_amount
rfi.schedule_impact = schedule_impact
rfi.schedule_days = schedule_days
._save_rfis()
._notify_response(rfi)
rfi
() -> RFI:
rfi = .rfis.get(rfi_number)
rfi:
ValueError()
rfi.status = RFIStatus.CLOSED
rfi.date_closed = date.today()
change_order_ref:
rfi.change_order_ref = change_order_ref
._save_rfis()
rfi
() -> [RFI]:
today = date.today()
[
rfi rfi .rfis.values()
rfi.status == RFIStatus.OPEN
rfi.date_required < today
]
() -> [RFI]:
today = date.today()
cutoff = today + timedelta(days=days)
[
rfi rfi .rfis.values()
rfi.status == RFIStatus.OPEN
today <= rfi.date_required <= cutoff
]
() -> [RFI]:
[r r .rfis.values() r.status == status]
() -> [RFI]:
[r r .rfis.values() r.assigned_to == assignee]
() -> :
all_rfis = (.rfis.values())
all_rfis:
{: }
open_rfis = [r r all_rfis r.status == RFIStatus.OPEN]
closed_rfis = [r r all_rfis r.status == RFIStatus.CLOSED]
response_times = []
rfi closed_rfis:
rfi.date_responded rfi.date_submitted:
days = (rfi.date_responded - rfi.date_submitted).days
response_times.append(days)
cost_rfis = [r r all_rfis r.cost_impact]
schedule_rfis = [r r all_rfis r.schedule_impact]
{
: (all_rfis),
: (open_rfis),
: (closed_rfis),
: (.get_overdue_rfis()),
: (response_times) / (response_times) response_times ,
: (cost_rfis),
: (r.cost_amount r cost_rfis),
: (schedule_rfis),
: (r.schedule_days r schedule_rfis),
: {
p.value: ([r r all_rfis r.priority == p])
p RFIPriority
},
: ._group_by_assignee(all_rfis)
}
() -> :
result = {}
rfi rfis:
rfi.assigned_to result:
result[rfi.assigned_to] = {: , : }
result[rfi.assigned_to][] +=
rfi.status == RFIStatus.OPEN:
result[rfi.assigned_to][] +=
result
():
()
()
()
():
()
()
() -> :
stats = .get_statistics()
report =
priority, count stats[].items():
report +=
report +=
assignee, data stats[].items():
report +=
output_path:
(output_path, ) f:
f.write(report)
report
__name__ == :
manager = RFIManager(project_id=)
rfi = manager.create_rfi(
subject=,
question=,
submitted_by=,
submitted_by_company=,
assigned_to=,
spec_section=,
drawing_ref=,
location=,
priority=RFIPriority.HIGH,
days_for_response=
)
()
manager.submit_rfi(rfi.rfi_number)
manager.respond_to_rfi(
rfi_number=rfi.rfi_number,
response=,
response_by=,
schedule_impact=,
schedule_days=
)
manager.close_rfi(rfi.rfi_number)
(manager.generate_report())
n8n Integration
name: RFI Notification Workflow
trigger:
type: webhook
path: /rfi-notification
steps:
- parse_rfi:
node: Code
code: |
return {
rfi_number: $json.rfi_number,
subject: $json.subject,
assigned_to: $json.assigned_to,
due_date: $json.date_required,
priority: $json.priority
};
- get_recipient:
node: Google Sheets
operation: readRows
sheet: Contacts
filter: role = "={{$json.assigned_to}}"
- send_email:
node: Email
to: "={{$json.email}}"
subject: "[RFI {{$json.rfi_number}}] {{$json.subject}}"
body: |
New RFI requires your response:
RFI
Subject: {{$json.subject}}
Priority: {{$json.priority}}
Due Date: {{}}
{{}}
{{}}
{{}}
[ ]
Templates
RFI Submission Template
## REQUEST FOR INFORMATION
**RFI Number:** [Auto-generated]
**Date:** [Today]
**Project:** [Project Name]
### QUESTION
**Subject:** [Brief title - max 80 characters]
**Specification Section:** [CSI number]
**Drawing Reference:** [Drawing number(s)]
**Location:** [Building/Floor/Area]
**Question:**
[Detailed question - be specific about what clarification is needed]
**Suggested Resolution:**
[If you have a proposed solution, include it here]
### ATTACHMENTS
- [ ] Relevant drawing sections
- [ ] Photos of field conditions
- [ ] Specification excerpts
### IMPACT ASSESSMENT
- Estimated Cost Impact: [ ] Yes [ ] No Amount: $_______
- Estimated Schedule Impact: [ ] Yes [ ] No Days: _______
- Work Stoppage: [ ] Yes [ ] No
**Response Required By:** [Date - default +7 days]
---
Submitted by: [Name, Company]
"A well-written RFI gets answered faster. Be specific, reference documents, and propose solutions."