Commit 74292b78 by Prayush Kumar

APP: upload a first edition, untested so far

parent da3d6411
# ICTS Housing Cell - Allocation Portal Web GUI
A responsive, responsive Web GUI dashboard application designed to automate the housing lottery allocation process for Postdoctoral Fellows and Graduate Students based on the priority pool policies of the ICTS Housing Cell.
---
## 1. Directory Structure
```text
2026/app/
├── allocator.py # Core allocation and priority pooling rules engine
├── server.py # Flask HTTP Web Server handling JSON APIs
├── run.sh # Startup script to initialize the application via terminal
├── README.md # Usage instructions and logic details (this file)
└── templates/
└── index.html # HTML5 Single Page Application Web Dashboard UI
```
---
## 2. Requirements
The application runs using the system's base conda environment which contains all required dependencies:
- **Python 3**
- **Flask**
- **Pandas**
- **Numpy**
---
## 3. How to Run the Web Application
1. Open your terminal.
2. Run the startup script directly:
```bash
./2026/app/run.sh
```
3. Open your browser and navigate to:
[http://localhost:5000](http://localhost:5000)
---
## 4. Web Dashboard Features
- **Toggle Mode**: Switch seamlessly between **Postdocs** and **Graduate Students** allocation models.
- **CSV Response Upload**: Drag-and-drop the standard responses CSV files (from Google Forms) directly into the portal.
- **Interactive Capacity Configuration**: Adjust the available slots for each housing category (such as hostels or apartments) in real time before triggering the lottery.
- **Custom Lottery Seed**: Enter a custom integer seed (e.g. `1780813800` or date seed) to get fully reproducible allotment order.
- **Interactive Results Table**: Browse allocations instantly. Filter, search by candidate name/email, and verify their satisfied preference rank or unallocated status.
- **Raw Export**: Download the generated allotment tables in `.txt` format (compatible with the existing format) or standard `.csv` files.
#!/bin/bash
# Navigate to app directory
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd "$DIR"
# Launch Flask server with base Conda Python which contains pandas/numpy
echo "Starting ICTS Housing Lottery Web GUI..."
echo "Open your browser at: http://localhost:5000"
/home/prayush/miniconda3/bin/python server.py
import os
import io
import json
import pandas as pd
from flask import Flask, request, jsonify, render_template, send_file
from allocator import (
parse_postdoc_csv,
parse_student_csv,
run_lottery,
DEFAULT_POSTDOC_AVAILABILITY,
DEFAULT_STUDENT_AVAILABILITY,
POSTDOC_HOUSING_MAPPING,
STUDENT_HOUSING_MAPPING
)
app = Flask(__name__, template_folder='templates')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/defaults', methods=['GET'])
def get_defaults():
# Return mapping and default availability numbers
return jsonify({
"postdoc": {
"mapping": POSTDOC_HOUSING_MAPPING,
"availability": DEFAULT_POSTDOC_AVAILABILITY
},
"student": {
"mapping": STUDENT_HOUSING_MAPPING,
"availability": DEFAULT_STUDENT_AVAILABILITY
}
})
@app.route('/api/allocate', methods=['POST'])
def allocate():
if 'file' not in request.files:
return jsonify({"error": "No file uploaded"}), 400
file = request.files['file']
mode = request.form.get('mode', 'postdoc')
seed_str = request.form.get('seed', '')
# Custom availability override
availability_override_json = request.form.get('availability', '{}')
try:
raw_avail = json.loads(availability_override_json)
# Convert keys to integers
availability = {int(k): int(v) for k, v in raw_avail.items()}
except Exception as e:
return jsonify({"error": f"Invalid availability structure: {str(e)}"}), 400
# Custom seed conversion
seed = None
if seed_str.strip() != "":
try:
seed = int(seed_str)
except ValueError:
seed = seed_str # fallback to string seed which run_lottery will handle
try:
# Read the file
stream = io.StringIO(file.stream.read().decode("utf-8"), newline=None)
df = pd.read_csv(stream)
except Exception as e:
return jsonify({"error": f"Error parsing CSV file: {str(e)}"}), 400
try:
if mode == "postdoc":
candidates = parse_postdoc_csv(df)
else:
candidates = parse_student_csv(df)
except Exception as e:
return jsonify({"error": f"Error extracting candidates: {str(e)}"}), 400
if not candidates:
return jsonify({"error": "No valid candidates found in CSV file. Check column mappings."}), 400
try:
results = run_lottery(candidates, mode=mode, availability=availability, seed=seed)
except Exception as e:
return jsonify({"error": f"Error executing lottery: {str(e)}"}), 400
# Format output for frontend JSON representation
return jsonify(results)
if __name__ == '__main__':
# Running locally on port 5000
app.run(host='0.0.0.0', port=5000, debug=True)
import os
import sys
import pandas as pd
from allocator import parse_postdoc_csv, parse_student_csv, run_lottery
# Paths resolved relative to script location
APP_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(os.path.dirname(APP_DIR), '2026')
def verify_postdocs():
csv_path = os.path.join(DATA_DIR, 'Housing Allotment Preference Form (PDF) 2026-27 (Responses) - Form Responses 1.csv')
txt_path = os.path.join(DATA_DIR, '2026__postdoc_housing_allocations.txt')
df = pd.read_csv(csv_path)
candidates = parse_postdoc_csv(df)
results = run_lottery(candidates, mode='postdoc', seed=1780813800)
# Read expected
expected = {}
with open(txt_path, 'r') as f:
for line in f:
if line.startswith('#') or not line.strip():
continue
parts = line.strip().split(',')
expected[parts[0].lower()] = int(parts[1])
# Read actual
actual = {}
for a in results['allocations']:
actual[a['name'].lower()] = a['housing_num']
for u in results['unallocated']:
actual[u['name'].lower()] = -1
print("Postdoc Shuffled Pool 5 list:")
print("Expected: ['suruj jyoti kalita', 'shanmugapriya prakasam', 'santana majee', 'nimisha pahuja', 'amit kumar', 'savita rani', 'adrian padellaro', 'georg stettinger', 'muktajyoti saha', 'anurag kaushal', 'mahesh gandikota', 'sounak biswas', 'ken kikuchi', 'mainak pal']")
print("Actual:", results['summary']['pool_order_log'].get(5))
mismatches = []
for name, exp_val in expected.items():
if name not in actual:
mismatches.append(f"Missing applicant {name}")
elif actual[name] != exp_val:
mismatches.append(f"Postdoc {name}: expected {exp_val}, got {actual[name]}")
if mismatches:
print("Postdoc mismatches found:")
for m in mismatches:
print(" ", m)
return False
else:
print("Postdoc allocations match expected output exactly!")
return True
def verify_students():
csv_path = os.path.join(DATA_DIR, 'Housing allotment Request form 2026 - Current Graduate students (Responses) - Form Responses 1.csv')
txt_path = os.path.join(DATA_DIR, '2026__student_housing_allocations.txt')
df = pd.read_csv(csv_path)
candidates = parse_student_csv(df)
results = run_lottery(candidates, mode='student', seed=1780813800)
# Read expected
expected = {}
with open(txt_path, 'r') as f:
for line in f:
if line.startswith('#') or not line.strip():
continue
parts = line.strip().split(',')
expected[parts[0].lower().strip().replace(" ", " ")] = int(parts[1])
# Read actual
actual = {}
for a in results['allocations']:
actual[a['name'].lower().strip().replace(" ", " ")] = a['housing_num']
for u in results['unallocated']:
actual[u['name'].lower().strip().replace(" ", " ")] = -1
print("Student Shuffled Pool 2 list:")
print("Expected: ['ankur barsode', 'mrinal jyoti powdel', 'akash maurya', 'jigyasa watwani', 'avi wadhwa', 'ritwik mukherjee', 'ashik h', 'rajarshi chattopadhyay', 'muhammed irshad p', 'santhiya p s', 'bikram pain', 'mayank kumar bijay', 'aditya thorat', 'alorika kar', 'shridhar vinayak', 'tamoghna ray', 'kaustubh singhi']")
print("Actual:", results['summary']['pool_order_log'].get(2))
mismatches = []
for name, exp_val in expected.items():
if name not in actual:
mismatches.append(f"Missing student {name}")
elif actual[name] != exp_val:
mismatches.append(f"Student {name}: expected {exp_val}, got {actual[name]}")
if mismatches:
print("Student mismatches found:")
for m in mismatches:
print(" ", m)
return False
else:
print("Student allocations match expected output exactly!")
return True
if __name__ == '__main__':
print("Executing Student Verification against expected notebook output...")
s_ok = verify_students()
if s_ok:
print("\nSUCCESS: All student allocations match notebook output exactly!")
sys.exit(0)
else:
print("\nFAILURE: Mismatches found in student allocations.")
sys.exit(1)
# ICTS Housing Cell - Allocation Logic & Policy Documentation (2026)
This document details the housing allotment rules, priority pooling policies, preference processing algorithms, and lottery mechanism used by the ICTS Housing Cell for allocating campus housing to **Postdoctoral Fellows (PDFs)** and **Graduate Students (PhD / Int-PhD)**.
---
## 1. Overview & Allotment Workflow
The housing allocation process automates the distribution of limited hostel rooms and campus apartments based on:
1. **Applicant Group**: Postdoctoral Fellows vs. Graduate Students.
2. **Priority Pools**: Institutional policy rules prioritizing medical needs, dependents, gender, and academic seniority.
3. **Preference Vectors**: Applicant choices ranked from 1 (most preferred) downwards.
4. **Lottery Tie-Breaking**: Pseudo-random shuffling within each priority pool using a fixed RNG seed for reproducibility.
5. **Sequential Greedy Allocation**: Candidate-by-candidate allocation in lottery rank order, assigning their highest-ranked available accommodation.
---
## 2. Accommodation Types & Mapping
### Postdoctoral Fellows (Index 0 – 7)
| Index | Accommodation Category | Description & Eligibility |
| :---: | :--- | :--- |
| `0` | On campus Studios | Studio apartments on campus |
| `1` | Hostel 1 non-1BHK | Shared 2BHK apartment in Hostel 1 |
| `2` | Hostel 2 | Shared 2BHK apartment in Hostel 2 |
| `3` | Hostel 3 | Single occupancy room in Hostel 3 |
| `4` | Hostel 4 | Single occupancy room in Hostel 4 |
| `5` | Hostel 5 | Single occupancy room in Hostel 5 |
| `6` | On campus 1BHK | 1BHK apartment on campus (Family / Married only) |
| `7` | Hostel 1 1BHK | 1BHK apartment in Hostel 1 (Family / Married only) |
### Graduate Students (Index 0 – 8)
| Index | Accommodation Category | Description & Eligibility |
| :---: | :--- | :--- |
| `0` | On campus (3BHK sharing) | Shared 3BHK apartment on campus |
| `1` | Hostel 1 non-1BHK | Shared 2BHK apartment in Hostel 1 |
| `2` | Hostel 2 | Shared 2BHK apartment in Hostel 2 (Master/non-master) |
| `3` | Hostel 3 | Single room in Hostel 3 |
| `4` | Hostel 4 | Single room in Hostel 4 |
| `5` | Hostel 5 | Single room in Hostel 5 |
| `6` | On campus 1BHK | Restricted / PDF priority |
| `7` | Hostel 1 1BHK | Restricted / PDF priority |
| `8` | On campus Studios | Restricted / PDF priority |
---
## 3. Preference Parsing & Order Construction
Google Form responses collect numerical ranks for each option (e.g. `1` for top choice, `2` for second, etc.).
- **Unranked Options**: Represented as `NaN` or `99`.
- **Conversion to Preference Vector**:
1. The raw rank list `[r_0, r_1, ..., r_N]` is converted into an ordered list of accommodation indices `[pref_0, pref_1, ...]`.
2. Choices with lower numerical ranks appear earlier in the preference vector.
3. Tied or repeated ranks are ordered deterministically in reverse column order.
4. Unranked choices (rank `99`) are appended at the end of the candidate's preference vector.
---
## 4. Priority Pool Rules
Applicants are grouped into priority pools. Allocations are executed pool by pool in numerical order (Pool 1 gets highest priority, followed by Pool 2, Pool 3, etc.).
### A. Postdoctoral Fellows Priority Pools (Pools 1 to 5)
1. **Pool 1 (Highest Priority - Medical & Disability Family)**:
- Must prefer Family accommodation (`student_or_postdoc == "postdoc_family"`).
- Must have a registered medical/disability condition (`disability_check == 1`).
- Must have at least 1 dependent (`num_dependents > 0`).
2. **Pool 2 (High Family Priority)**:
- Family postdocs with `num_dependents > 2`, OR
- Female family postdocs with `num_dependents >= 2`.
3. **Pool 3 (Family with 2+ Dependents)**:
- Family postdocs with `num_dependents >= 2`.
4. **Pool 4 (Family with 1 Dependent)**:
- Family postdocs with `num_dependents == 1`.
5. **Pool 5 (General Postdocs)**:
- All single postdocs and remaining family postdocs not meeting higher pool criteria.
### B. Graduate Students Priority Pools (Pools 1 to 3)
*Note on Academic Year*: Before the start of the new session in July, student academic years are normalized (e.g. 1st year incoming is year 1).
1. **Pool 1 (Highest Priority - 1st Year Female Students)**:
- Female graduate students entering or in 1st year (`female == 1` AND `year == 1`).
2. **Pool 2 (1st Year Male & Senior PhD Students)**:
- 1st year male students (`year == 1`), OR
- Senior PhD students: PhD year $\ge 5$ OR Int-PhD year $\ge 6$.
3. **Pool 3 (General Graduate Students)**:
- All other registered graduate students (2nd, 3rd, 4th year PhD; 2nd, 3rd, 4th, 5th year Int-PhD).
---
## 5. Lottery & Allocation Algorithm
1. **Random Seed Initialization**:
- A random seed is configured (e.g., standard noon timestamp `datetime(2026, 6, 7, 12, 0, 0)`).
2. **Sequential Pool Execution**:
- For each pool ID in `[1, 2, ...]`:
- **Shuffle Candidates**: Randomize candidate order within the pool using `random.shuffle(pool_candidates)`.
- **Greedy Allocation**: For each candidate in shuffled order:
- Iterate through candidate's preference vector `[pref_0, pref_1, ...]`.
- Assign the first housing category `pref_i` with `remaining_availability[pref_i] > 0`.
- Decrement `remaining_availability[pref_i]` by 1.
- If all preferred housing options are exhausted (capacity 0), the candidate remains unallocated.
3. **Output Generation**:
- Output contains participant name, email, priority pool ID, allocated housing option ID, allocated housing option name, and preference rank satisfied.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment